MediaWiki:Gadget-MarkAdmins.js
/**
*  Flag administrators and special user group members with a letter
*  in parenthesis behind links that go into their user namespace.
*  E.g. Didym -> Didym (A)
*
*  @license
*  @link https://commons.wikimedia.org/wiki/MediaWiki:Gadget-markAdmins.js
*
*  @author Euku - 2005, PDD, Littl, Guandalug
*  @author Didym - 2014
*  @author Rillke <https://blog.rillke.com> - 2014
*  @contributor Perhelion - 2017
*
*  @author Dragoniez - 2023, radically modified the original
*  This modified version differs from the original in three main respects:
*   - Does not create a new node in user link anchor tags. It only assigns
*     a CSS class to the relevant tags and shows group information as a
*     pseudo-element of the class (presumably faster, and $(anchor).text()
*     doesn't include group notations).
*   - Does not suppport configurations via user common.js. Instead, it
*     provides configurations on Special:MarkAdminsConfig.
*   - User contribs links are also marked.
*
*  @requires MediaWiki:Gadget-MarkAdmins-data.json
*  @requires MediaWiki:Gadget-MarkAdmins-updater.js
*/
//
'use strict';
(function(mw, $) {
var MarkAdmins = mw.libs.MarkAdmins = {
images: {
loading: '<img src="//upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" style="vertical-align: middle; height: 1em; border: 0;">',
check: '<img src="//upload.wikimedia.org/wikipedia/commons/f/fb/Yes_check.svg" style="vertical-align: middle; height: 1em; border: 0;">',
cross: '<img src="//upload.wikimedia.org/wikipedia/commons/a/a2/X_mark.svg" style="vertical-align: middle; height: 1em; border: 0;">'
},
config: {},
defaults: {
groups: {
'sysop': {
label: 'A',
localized: '管理者',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:管理者',
enabled: true
},
'suppress': {
label: 'OS',
localized: 'オーバーサイト',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:オーバーサイトの方針',
enabled: true
},
'checkuser': {
label: 'CU',
localized: 'チェックユーザー',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:チェックユーザーの方針',
enabled: true
},
'bureaucrat': {
label: 'B',
localized: 'ビューロクラット',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:ビューロクラット',
enabled: true
},
'abusefilter': {
label: 'FE',
localized: '編集フィルター編集者',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:編集フィルター編集者',
enabled: true
},
'interface-admin': {
label: 'IA',
localized: 'インターフェース管理者',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:インターフェース管理者',
enabled: true
},
'accountcreator': {
label: 'AC',
localized: 'アカウント作成者',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:アカウント作成者',
enabled: true
},
'bot': {
label: 'Bot',
localized: 'ボット',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:Bot',
enabled: true
},
'eliminator': {
label: 'E',
localized: '削除者',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:削除者',
enabled: true
},
'rollbacker': {
label: 'RB',
localized: '巻き戻し者',
link: 'https://ja.wikipedia.org/wiki/Wikipedia:巻き戻し者',
enabled: true
},
'founder': {
label: 'F',
localized: '創設者',
link: 'https://meta.wikimedia.org/wiki/Founder/ja',
enabled: true
},
'steward': {
label: 'S',
localized: 'スチュワード',
link: 'https://meta.wikimedia.org/wiki/Stewards/ja',
enabled: true
},
'ombuds': {
label: 'Omb',
localized: 'オンブズ委員',
link: 'https://meta.wikimedia.org/wiki/Ombuds_commission/ja',
enabled: true
},
'staff': {
label: 'Staff',
localized: 'スタッフ',
link: 'https://wikimediafoundation.org/role/staff-contractors/',
enabled: true
},
'sysadmin': {
label: 'SA',
localized: 'システム管理者',
link: 'https://meta.wikimedia.org/wiki/System_administrators/ja',
enabled: true
},
'global-sysop': {
label: 'GS',
localized: 'グローバル管理者',
link: 'https://meta.wikimedia.org/wiki/Global_sysops/ja',
enabled: true
},
'abusefilter-maintainer': {
label: 'GFE',
localized: '編集フィルター保守員',
link: 'https://meta.wikimedia.org/wiki/Abuse_filter_maintainer/ja',
enabled: true
},
'abusefilter-helper': {
label: 'GFH',
localized: '編集フィルター閲覧者',
link: 'https://meta.wikimedia.org/wiki/Abuse_filter_helpers/ja',
enabled: true
},
'global-interface-editor': {
label: 'GIE',
localized: 'グローバルインターフェース編集者',
link: 'https://meta.wikimedia.org/wiki/Interface_editors/ja',
enabled: true
},
'global-bot': {
label: 'GBot',
localized: 'グローバルボット',
link: 'https://meta.wikimedia.org/wiki/Bot_policy/ja#global',
enabled: true
},
'global-deleter': {
label: 'GE',
localized: 'グローバル削除者',
link: 'https://meta.wikimedia.org/wiki/Global_deleters/ja',
enabled: true
},
'global-rollbacker': {
label: 'GRB',
localized: 'グローバル巻き戻し者',
link: 'https://meta.wikimedia.org/wiki/Global_rollback/ja',
enabled: true
},
'vrt-permissions': {
label: 'VRT',
localized: '問い合わせ対応ボランティアチーム',
link: 'https://meta.wikimedia.org/wiki/Volunteer_Response_Team/ja',
enabled: true
},
'global-renamer': {
label: 'GRN',
localized: 'グローバル利用者名変更者',
link: 'https://meta.wikimedia.org/wiki/Global_renamers/ja',
enabled: true
},
'wmf-officeit': {
label: 'WMF OIT',
localized: 'WMFオフィスIT',
link: 'https://meta.wikimedia.org/wiki/Meta:WMF_Office_IT/ja',
enabled: true
},
'wmf-supportsafety': {
label: 'WMF T&S',
localized: 'WMFサポートと安全班',
link: 'https://meta.wikimedia.org/wiki/Meta:WMF_Support_and_Safety/ja',
enabled: true
}
},
runOn: ['Special', , 'User', 'User_talk', 'Project', 'File', 'Help', 'Portal', 'プロジェクト'],
runOnOptions: [
{
data: 'Special', // Canonical namespace name
label: '特別' // Corresponding label to show in multi-select
},
{
data: ,
label: '(標準)'
},
{
data: 'User',
label: '利用者'
},
{
data: 'User_talk',
label: '利用者‐会話'
},
{
data: 'Project',
label: 'Wikipedia'
},
{
data: 'Project_talk',
label: 'Wikipedia‐ノート'
},
{
data: 'File',
label: 'ファイル'
},
{
data: 'File_talk',
label: 'ファイル‐ノート'
},
{
data: 'MediaWiki',
label: 'MediaWiki'
},
{
data: 'MediaWiki_talk',
label: 'MediaWiki‐ノート'
},
{
data: 'Template',
label: 'Template'
},
{
data: 'Template_talk',
label: 'Template‐ノート'
},
{
data: 'Help',
label: 'Help'
},
{
data: 'Help_talk',
label: 'Help‐ノート'
},
{
data: 'Category',
label: 'Category'
},
{
data: 'Category_talk',
label: 'Category‐ノート'
},
{
data: 'Portal',
label: 'Portal'
},
{
data: 'Portal‐ノート',
label: 'Portal‐ノート'
},
{
data: 'プロジェクト',
label: 'プロジェクト'
},
{
data: 'プロジェクト‐ノート',
label: 'プロジェクト‐ノート'
},
{
data: 'Module',
label: 'モジュール'
},
{
data: 'Module_talk',
label: 'モジュール‐ノート'
},
],
runOnHistory: true,
runOnTalk: true,
runOnDiff: true,
markSubpages: false,
markMyself: true,
markContribs: true
},
/**
* @type {number}
* @static
* @readonly
*/
nsNumber: mw.config.get('wgNamespaceNumber'),
init: function() {
var isOnConfigPage = MarkAdmins.nsNumber === -1 && /^(MarkAdminsConfig|MAC)$/i.test(mw.config.get('wgTitle'));
var dependencies = ['mediawiki.user', 'mediawiki.util', 'mediawiki.api', 'oojs-ui-widgets'];
if (!isOnConfigPage) dependencies.splice(2, 2);
$.when(mw.loader.using(dependencies), $.ready).then(function() {
MarkAdmins.createStyleTag();
MarkAdmins.loadUpdater();
MarkAdmins.mergeConfig(isOnConfigPage);
});
},
/** @static */
createStyleTag: function() {
var style = document.createElement('style');
style.textContent =
// Main pseudo selector to show user groups
'.adminMark::after {' +
'content: attr(data-groupmarker);' +
'font-weight: bold;' +
'font-size: 85%;' +
'vertical-align: middle;' +
'margin-left: 0.3em;' +
'}' +
// Config page
// Headers
'.mac-config-section-header h4 {' +
'border-bottom: 1px solid ;' +
'margin-top: 0.2em;' +
'}' +
// Border for config body
'#mac-body > div {' +
'border: 1px solid ;' +
'}' +
'#mac-body > div:not(:last-child) {' +
'border-bottom: none;' +
'}' +
// Change background color of table rows
'#mac-config-body table tr:nth-child(odd):not(.mac-config-section-header):not(#mac-config-saving):not(#mac-config-lastsaved) td {' +
'background: ;' +
'}' +
// Right-margin for checkboxes
'#mac-config-body input[type=checkbox] {' +
'margin-right: 0.5em;' +
'}' +
// "Right-margin" for labels that have text fields on their right
'.mac-config-textinputlabel {' +
'display: inline-block;' +
'width: 8ch;' +
'}' +
// Margin for buttons
'#mac-config-body input[type=button] {' +
'margin: 0.5em;' +
'}';
document.head.appendChild(style);
},
/** @static */
loadUpdater: function() {
// The updater can be used only by sysops. See the descriptions in the module.
if (mw.config.get('wgUserGroups').indexOf('sysop') === -1) return;
try {
// @ts-ignore "XX is not a module."
require('./MarkAdmins-updater.js');
}
catch(err) {
$.getScript('https://ja.wikipedia.org/w/index.php?title=MediaWiki:Gadget-MarkAdmins-updater.js&action=raw&ctype=text/javascript');
}
},
/**
* @type {string}
* @static
* @readonly
*/
userOptionName: 'userjs-MarkAdminsCfg',
/**
* Stores user group JSON data. This property being null means that the script is loaded externally.
* @type {Object.>|null}
*/
usersData: (function() {
try {
// require() works only when loaded via gadgets-definition
return require('./MarkAdmins-data.json');
}
catch(err) {
return null;
}
})(),
mergeConfig: function(isOnConfigPage) {
// Merge new configuration
var MarkAdminsCfg = mw.user.options.get(MarkAdmins.userOptionName);
var cfg = $.extend(
true,
MarkAdmins.config,
MarkAdmins.defaults,
MarkAdminsCfg ? JSON.parse(MarkAdminsCfg) : {}
);
if (isOnConfigPage) {
return MarkAdmins.createConfigPage(cfg);
} else {
MarkAdmins.createPortletLink();
}
// Namespace run conditions
if (!(cfg.runOn.indexOf(mw.config.get('wgCanonicalNamespace')) !== -1 ||
cfg.runOnHistory && mw.config.get('wgAction') === 'history' ||
cfg.runOnTalk && MarkAdmins.nsNumber % 2 ||
cfg.runOnDiff && !!mw.util.getParamValue('diff')) ||
mw.config.get('wgCanonicalSpecialPageName') === 'CentralAuth'
) {
return;
}
// Proceed to markup
if (MarkAdmins.usersData) {
mw.hook('wikipage.content').add(function($content) {
MarkAdmins.markUser($content);
});
} else {
$.get('https://ja.wikipedia.org/w/index.php?title=MediaWiki:Gadget-MarkAdmins-data.json&action=raw')
.then(function(usersData) {
if (!usersData) return;
usersData = JSON.parse(usersData);
MarkAdmins.usersData = usersData;
mw.hook('wikipage.content').add(function($content) {
MarkAdmins.markUser($content);
});
}).catch(function(err) {
console.error(err);
});
}
},
createConfigPage: function(cfg) {
document.title = 'MarkAdminsの設定 - Wikipedia';
// Dividing the source html just because deep nests make it hard to see
var createConfigBody = function(tableRowsHtml) {
var html =
+
+
+
'<b>MarkAdmins config</b>' +
+
+
// <-- ToC
+
+
+
;
return html;
};
// Create ToC
var toc = document.createElement('div');
toc.id = 'mac-toc';
toc.style.cssText = 'margin: 0.5em; padding: 0.5em; display: table; background: ; border: 1px solid ;';
var toctitle = document.createElement('div');
toc.appendChild(toctitle);
toctitle.id = 'mac-toctitle';
var toch2 = document.createElement('b');
toctitle.appendChild(toch2);
toch2.appendChild(document.createTextNode('目次'));
var toctoggle = document.createElement('span');
toctitle.appendChild(toctoggle);
toctoggle.id = 'mac-toctoggle';
toctoggle.style.cssText = 'display: inline-block; margin-left: 1em;';
toctoggle.appendChild(document.createTextNode('['));
var toctogglelink = document.createElement('a');
toctoggle.appendChild(toctogglelink);
toctogglelink.href = '#mac-tocshowhide';
toctogglelink.textContent = '隠す';
toctoggle.appendChild(document.createTextNode(']'));
var tocol = document.createElement('ol'); // This is where toc items go in
toc.appendChild(tocol);
if (mw.config.get('skin') !== 'timeless') tocol.style.marginLeft = '2em';
toctogglelink.addEventListener('click', function() { // For show/hide
var $tocol = $(tocol);
$tocol.toggle();
if ($tocol.find(':visible').length) {
toctogglelink.textContent = '隠す';
} else {
toctogglelink.textContent = '表示';
}
}, false);
/**
* @param {string} [text]
* @param {string|null} [id]
* @param {string|null} [clss]
* @param {string|null} [cssText]
* @returns {string}
*/
var wrapTextBySpan = function(text, id, clss, cssText) {
var span = document.createElement('span');
if (text) span.appendChild(document.createTextNode(text));
if (id) span.id = id;
if (clss) span.classList.add(clss);
if (cssText) span.style.cssText = cssText;
return span.outerHTML;
};