User:SaoMikoto/js/Usergroup.js: Difference between revisions

From Test Wiki
Jump to navigation Jump to search
Content deleted Content added
m // Edit via InPageEdit
m // Edit via InPageEdit
 
Line 1:
// <nowiki>
(function () {
 
(function() {
'use strict';
 
// 用户组配置
const USER_GROUPS = {
'sysadmin': { label: '系', color: '#FF4500', name: '系统管理员' },
'steward': { label: '裁', color: '#9400D3', name: '监管员' },
'bureaucrat': { label: '行', color: '#008080', name: '行政员' },
'checkuser': { label: '查', color: '#4B0082', name: '用户查核员' },
'suppress': { label: '监', color: '#000000', name: '监督员' },
'sysop': { label: '管', color: '#2E8B57', name: '管理员' },
'interface-admin': { label: '界', color: '#FF8C00', name: '界面管理员' },
'interwiki-admin': { label: '域', color: '#4682B4', name: '跨wiki管理员' },
'electionadmin': { label: '选', color: '#9932CC', name: '选举管理员' },
'abusefilter-admin': { label: '滤', color: '#DC143C', name: '滥用过滤器管理员' },
'translateadmin': { label: '译', color: '#008B8B', name: '翻译管理员' },
'chatmod': { label: '聊', color: '#20B2AA', name: '聊天版主' },
'patroller': { label: '巡', color: '#3CB371', name: '巡查员' },
'autopatrol': { label: '免', color: '#6B8E23', name: '巡查豁免者' },
'bot': { label: '机', color: '#696969', name: '机器人' },
'confirmed': { label: '确', color: '#4682B4', name: '确认用户' }
};
 
const GROUP_ORDER = Object.keys(USER_GROUPS);
const userGroupCacheCACHE = new Map();
const CACHE_DURATIONCACHE_TTL = 5 * 60 * 1000;
 
function extractUsername(href) {
// 获取用户组信息
if (!href) return null;
function getUserGroups(usernames, callback) {
try {
if (!usernames || usernames.length === 0) {
callbackconst decoded = decodeURIComponent({}href);
const match = decoded.match(/[?/]title=User:([^&#/?]+)|[?/]User:([^&#/?]+)/i);
return;
const user = match ? (match[1] || match[2]) : null;
return user ? user.replace(/_/g, ' ') : null;
} catch {
return null;
}
}
 
function getUserGroups(usernames, callback) {
var uncachedUsers = [];
varif result(!usernames.length) =return callback({});
const api = new mw.Api();
const results = {};
const toFetch = [];
 
usernames.forEach(function(username)name => {
varconst cached = userGroupCacheCACHE.get(usernamename);
if (cached && Date.now() - cached.timestamptime < CACHE_DURATIONCACHE_TTL) {
resultresults[usernamename] = cached.groups;
} else {
uncachedUserstoFetch.push(usernamename);
}
});
 
if (uncachedUsers!toFetch.length) ===return 0callback(results) {;
callback(result);
return;
}
 
var api = new mw.Api();
api.get({
action: 'query',
list: 'users',
ususers: uncachedUserstoFetch.join('|'),
usprop: 'groups',
formatversion: 2
}).done(function(response)res => {
if (response.query && responseres.query?.users || []).forEach(u => {
responseconst groups = (u.querygroups || []).users.forEachfilter(function(user)g {=> USER_GROUPS[g]);
results[u.name] = if (user.groups && !user.missing) {;
CACHE.set(u.name, { groups, time: var relevantGroups = user.groupsDate.filternow(function(group) {});
return USER_GROUPS[group]});
}callback(results);
}).fail(() => callback(results));
result[user.name] = relevantGroups;
userGroupCache.set(user.name, {
groups: relevantGroups,
timestamp: Date.now()
});
}
});
}
callback(result);
}).fail(function(error) {
console.warn('获取用户组信息失败:', error);
callback(result);
});
}
 
function createGroupIndicatormakeBadge(groups) {
if (!groups || groups?.length === 0) return null;
const container = document.createElement('sup');
 
container.style.cssText = 'font-size:85%;vertical-align:super;margin-left:2px;line-height:1;';
var sortedGroups = groups.sort(function(a, b) {
groups.sort((a, b) return=> GROUP_ORDER.indexOf(a) - GROUP_ORDER.indexOf(b);)
} .forEach((g, i); => {
const cfg = USER_GROUPS[g];
 
var container = document.createElement if ('sup'!cfg) return;
const span = document.createElement('span');
container.style.cssText =
'font-size: 85%;' + span.textContent = cfg.label;
'vertical-align: super;' + span.title = cfg.name;
span.style.cssText = `color:${cfg.color};cursor:help;${i ? 'margin-left: 2px1px;' +: ''}`;
'line-height: 1;' container.appendChild(span);
});
 
sortedGroups.forEach(function(group, index) {
var config = USER_GROUPS[group];
if (!config) return;
 
var span = document.createElement('span');
span.textContent = config.label;
span.style.cssText =
'color: ' + config.color + ';' +
'cursor: help;' +
(index > 0 ? 'margin-left: 1px;' : '');
span.title = config.name;
 
container.appendChild(span);
});
 
return container;
}
 
function processUserLinks() {
varconst userLinkslinks = document.querySelectorAll('.mw-userlink, .plainlinks .userlink');
if (userLinks!links.length === 0) return;
 
varconst usernamesmap = []new Map();
var linkMaplinks.forEach(link => new Map();{
 
userLinks.forEach(function(link) {
if (link.dataset.groupProcessed) return;
const name = link.classList.contains('mw-userlink')
? extractUsername(link.href)
: link.textContent.trim();
if (!name) return;
link.dataset.groupProcessed = 'true';
if (!map.has(name)) map.set(name, []);
map.get(name).push(link);
});
 
const names = [...map.keys()];
if (!names.length) return;
 
getUserGroups(names, var usernamedata => '';{
for (const [name, groups] of Object.entries(data)) {
if (link!groups?.classList.contains('mw-userlink')length) {continue;
varconst hrefbadge = link.getAttributemakeBadge('href'groups);
if (href!badge) {continue;
for (const link of var match = hrefmap.match(/User:get(name) || [^/?#]+)/); {
if (matchlink.nextElementSibling?.tagName === 'SUP') {continue;
username = decodeURIComponentlink.parentNode.insertBefore(match[1])badge.replacecloneNode(/_/gtrue), ' 'link.nextSibling);
}
}
} else if (link.classList.contains('userlink')) {
username = link.textContent.trim();
}
 
if (username && !linkMap.has(username)) {
usernames.push(username);
linkMap.set(username, []);
}
if (username) {
linkMap.get(username).push(link);
link.dataset.groupProcessed = 'true';
}
});
 
if (usernames.length === 0) return;
 
// 获取用户组信息
getUserGroups(usernames, function(userGroups) {
Object.keys(userGroups).forEach(function(username) {
var groups = userGroups[username];
var links = linkMap.get(username);
if (!links || groups.length === 0) return;
 
var indicator = createGroupIndicator(groups);
if (!indicator) return;
 
links.forEach(function(link) {
if (link.nextElementSibling && link.nextElementSibling.tagName === 'SUP') {
return;
}
link.parentNode.insertBefore(indicator.cloneNode(true), link.nextSibling);
});
});
});
}
 
// 初始化小工具
function init() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', processUserLinks);
} else {processUserLinks();
processUserLinks();
}
 
const observer = new MutationObserver(function(mutations)muts => {
letfor shouldProcess(const =m false;of muts) {
mutations.forEach if (function[...m.addedNodes].some(mutation)n {=>
if (mutation n.typenodeType === 1 && n.querySelector?.('childList.mw-userlink, .plainlinks .userlink') {
mutation.addedNodes.forEach(function(node)) {
if setTimeout(node.nodeTypeprocessUserLinks, === Node.ELEMENT_NODE100) {;
if (node.querySelector('.mw-userlink, .plainlinks .userlink')) {break;
shouldProcess = true;
}
}
});
}
});
if (shouldProcess) {
setTimeout(processUserLinks, 100);
}
}).observe(document.body, { childList: true, subtree: true });
});
 
observer.observe(document.body, {
childList: true,
subtree: true
});
}
 
if (typeof mw !== 'undefined' && mw.Api) {
(mw.Api ? Promise.resolve() : mw.loader.using('mediawiki.api')).then(init);
init();
} else {
if (typeof mw !== 'undefined') {
mw.loader.using('mediawiki.api').then(init);
}
}
 
})();
 
// </nowiki>

Latest revision as of 11:32, 5 October 2025

// <nowiki>
(function () {
    'use strict';

    const USER_GROUPS = {
        sysadmin: { label: '系', color: '#FF4500', name: '系统管理员' },
        steward: { label: '裁', color: '#9400D3', name: '监管员' },
        bureaucrat: { label: '行', color: '#008080', name: '行政员' },
        checkuser: { label: '查', color: '#4B0082', name: '用户查核员' },
        suppress: { label: '监', color: '#000000', name: '监督员' },
        sysop: { label: '管', color: '#2E8B57', name: '管理员' },
        'interface-admin': { label: '界', color: '#FF8C00', name: '界面管理员' },
        'interwiki-admin': { label: '域', color: '#4682B4', name: '跨wiki管理员' },
        electionadmin: { label: '选', color: '#9932CC', name: '选举管理员' },
        'abusefilter-admin': { label: '滤', color: '#DC143C', name: '滥用过滤器管理员' },
        translateadmin: { label: '译', color: '#008B8B', name: '翻译管理员' },
        chatmod: { label: '聊', color: '#20B2AA', name: '聊天版主' },
        patroller: { label: '巡', color: '#3CB371', name: '巡查员' },
        autopatrol: { label: '免', color: '#6B8E23', name: '巡查豁免者' },
        bot: { label: '机', color: '#696969', name: '机器人' },
        confirmed: { label: '确', color: '#4682B4', name: '确认用户' }
    };
    const GROUP_ORDER = Object.keys(USER_GROUPS);
    const CACHE = new Map();
    const CACHE_TTL = 5 * 60 * 1000;

    function extractUsername(href) {
        if (!href) return null;
        try {
            const decoded = decodeURIComponent(href);
            const match = decoded.match(/[?/]title=User:([^&#/?]+)|[?/]User:([^&#/?]+)/i);
            const user = match ? (match[1] || match[2]) : null;
            return user ? user.replace(/_/g, ' ') : null;
        } catch {
            return null;
        }
    }

    function getUserGroups(usernames, callback) {
        if (!usernames.length) return callback({});
        const api = new mw.Api();
        const results = {};
        const toFetch = [];

        usernames.forEach(name => {
            const cached = CACHE.get(name);
            if (cached && Date.now() - cached.time < CACHE_TTL) {
                results[name] = cached.groups;
            } else {
                toFetch.push(name);
            }
        });

        if (!toFetch.length) return callback(results);

        api.get({
            action: 'query',
            list: 'users',
            ususers: toFetch.join('|'),
            usprop: 'groups',
            formatversion: 2
        }).done(res => {
            (res.query?.users || []).forEach(u => {
                const groups = (u.groups || []).filter(g => USER_GROUPS[g]);
                results[u.name] = groups;
                CACHE.set(u.name, { groups, time: Date.now() });
            });
            callback(results);
        }).fail(() => callback(results));
    }

    function makeBadge(groups) {
        if (!groups?.length) return null;
        const container = document.createElement('sup');
        container.style.cssText = 'font-size:85%;vertical-align:super;margin-left:2px;line-height:1;';
        groups.sort((a, b) => GROUP_ORDER.indexOf(a) - GROUP_ORDER.indexOf(b))
            .forEach((g, i) => {
                const cfg = USER_GROUPS[g];
                if (!cfg) return;
                const span = document.createElement('span');
                span.textContent = cfg.label;
                span.title = cfg.name;
                span.style.cssText = `color:${cfg.color};cursor:help;${i ? 'margin-left:1px;' : ''}`;
                container.appendChild(span);
            });
        return container;
    }

    function processUserLinks() {
        const links = document.querySelectorAll('.mw-userlink, .plainlinks .userlink');
        if (!links.length) return;

        const map = new Map();
        links.forEach(link => {
            if (link.dataset.groupProcessed) return;
            const name = link.classList.contains('mw-userlink')
                ? extractUsername(link.href)
                : link.textContent.trim();
            if (!name) return;
            link.dataset.groupProcessed = 'true';
            if (!map.has(name)) map.set(name, []);
            map.get(name).push(link);
        });

        const names = [...map.keys()];
        if (!names.length) return;

        getUserGroups(names, data => {
            for (const [name, groups] of Object.entries(data)) {
                if (!groups?.length) continue;
                const badge = makeBadge(groups);
                if (!badge) continue;
                for (const link of map.get(name) || []) {
                    if (link.nextElementSibling?.tagName === 'SUP') continue;
                    link.parentNode.insertBefore(badge.cloneNode(true), link.nextSibling);
                }
            }
        });
    }

    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', processUserLinks);
        } else processUserLinks();

        new MutationObserver(muts => {
            for (const m of muts) {
                if ([...m.addedNodes].some(n =>
                    n.nodeType === 1 && n.querySelector?.('.mw-userlink, .plainlinks .userlink')
                )) {
                    setTimeout(processUserLinks, 100);
                    break;
                }
            }
        }).observe(document.body, { childList: true, subtree: true });
    }

    if (typeof mw !== 'undefined') {
        (mw.Api ? Promise.resolve() : mw.loader.using('mediawiki.api')).then(init);
    }
})();
// </nowiki>