User:SaoMikoto/js/Usergroup.js: Difference between revisions
From Test Wiki
Content deleted Content added
// Edit via InPageEdit |
m // Edit via InPageEdit |
||
| (4 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
// <nowiki> |
// <nowiki> |
||
(function () { |
|||
(function() { |
|||
'use strict'; |
'use strict'; |
||
// 用户组配置 |
|||
const USER_GROUPS = { |
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 GROUP_ORDER = Object.keys(USER_GROUPS); |
||
const |
const CACHE = new Map(); |
||
const |
const CACHE_TTL = 5 * 60 * 1000; |
||
function extractUsername(href) { |
|||
// 获取用户组信息 |
|||
if (!href) return null; |
|||
function getUserGroups(usernames, callback) { |
|||
try { |
|||
if (!usernames || usernames.length === 0) { |
|||
const 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 = []; |
|||
if (!usernames.length) return callback({}); |
|||
const api = new mw.Api(); |
|||
const results = {}; |
|||
const toFetch = []; |
|||
usernames.forEach( |
usernames.forEach(name => { |
||
const cached = CACHE.get(name); |
|||
if (cached && Date.now() - cached. |
if (cached && Date.now() - cached.time < CACHE_TTL) { |
||
results[name] = cached.groups; |
|||
} else { |
} else { |
||
toFetch.push(name); |
|||
} |
} |
||
}); |
}); |
||
if ( |
if (!toFetch.length) return callback(results); |
||
callback(result); |
|||
return; |
|||
} |
|||
var api = new mw.Api(); |
|||
api.get({ |
api.get({ |
||
action: 'query', |
action: 'query', |
||
list: 'users', |
list: 'users', |
||
ususers: |
ususers: toFetch.join('|'), |
||
usprop: 'groups', |
usprop: 'groups', |
||
formatversion: 2 |
formatversion: 2 |
||
}).done( |
}).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)); |
|||
result[user.name] = relevantGroups; |
|||
userGroupCache.set(user.name, { |
|||
groups: relevantGroups, |
|||
timestamp: Date.now() |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
callback(result); |
|||
}).fail(function(error) { |
|||
console.warn('获取用户组信息失败:', error); |
|||
callback(result); |
|||
}); |
|||
} |
} |
||
function |
function makeBadge(groups) { |
||
if (!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;'; |
|||
var sortedGroups = groups.sort(function(a, b) { |
|||
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'); |
|||
container.style.cssText = |
|||
span.textContent = cfg.label; |
|||
span.title = cfg.name; |
|||
'margin-left: |
span.style.cssText = `color:${cfg.color};cursor:help;${i ? 'margin-left:1px;' : ''}`; |
||
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; |
return container; |
||
} |
} |
||
function processUserLinks() { |
function processUserLinks() { |
||
const links = document.querySelectorAll('.mw-userlink, .plainlinks .userlink'); |
|||
if ( |
if (!links.length) return; |
||
var usernames = []; |
|||
var linkMap = new Map(); |
|||
const map = new Map(); |
|||
links.forEach(link => { |
|||
if (link.dataset.groupProcessed) return; |
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; |
|||
if (link.classList.contains('mw-userlink')) { |
|||
getUserGroups(names, data => { |
|||
var href = link.getAttribute('href'); |
|||
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); |
|||
} |
} |
||
} 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() { |
function init() { |
||
if (document.readyState === 'loading') { |
if (document.readyState === 'loading') { |
||
document.addEventListener('DOMContentLoaded', processUserLinks); |
document.addEventListener('DOMContentLoaded', processUserLinks); |
||
} else |
} else processUserLinks(); |
||
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; |
|||
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' |
if (typeof mw !== 'undefined') { |
||
(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> |
// </nowiki> |
||