MediaWiki:Common.js:修订间差异
MediaWiki界面页面
更多操作
无编辑摘要 |
无编辑摘要 |
||
| (未显示同一用户的4个中间版本) | |||
| 第139行: | 第139行: | ||
close: close | close: close | ||
}; | }; | ||
})(); | })(); | ||
| 第218行: | 第172行: | ||
Rail.init(); | Rail.init(); | ||
} | } | ||
/* ===== 工具栏账户信息动态填充 ===== */ | |||
(function () { | |||
var HIDDEN_GROUPS = ['*', 'user', 'autoconfirmed', 'emailconfirmed']; | |||
var GROUP_NAME_FALLBACK = { | |||
'sysop': '策展人', | |||
'bureaucrat': '行政员', | |||
'interface-admin': '织界者', | |||
'interface_admin': '织界者', | |||
'user': '旅人', | |||
'autoconfirmed': '自动确认用户' | |||
}; | |||
function formatDate(ms) { | |||
if (!ms) return '—'; | |||
var d = new Date(ms); | |||
return d.getFullYear() + '-' + | |||
String(d.getMonth() + 1).padStart(2, '0') + '-' + | |||
String(d.getDate()).padStart(2, '0'); | |||
} | |||
function isMissing(msg) { | |||
if (!msg) return true; | |||
// 消息未加载时 MediaWiki 会返回 ⧼key⧽ 形式 | |||
return msg.indexOf('\u29FC') !== -1 || msg.indexOf('⧼') !== -1; | |||
} | |||
function getGroupName(g) { | |||
var txt = mw.msg('group-' + g + '-member'); | |||
if (!isMissing(txt)) return txt; | |||
return GROUP_NAME_FALLBACK[g] || g; | |||
} | |||
function getGroupUrl(g) { | |||
var page = mw.msg('grouppage-' + g); | |||
if (!isMissing(page)) { | |||
return mw.util.getUrl(page); | |||
} | |||
return mw.util.getUrl('Anachronism:' + getGroupName(g)); | |||
} | |||
function getGroupHtml(groups) { | |||
var visible = groups.filter(function (g) { | |||
return HIDDEN_GROUPS.indexOf(g) === -1; | |||
}); | |||
if (!visible.length) return '无'; | |||
return visible.map(function (g) { | |||
var name = getGroupName(g); | |||
var url = getGroupUrl(g); | |||
return '<a href="' + url + '" class="rail-group-tag">' + name + '</a>'; | |||
}).join(''); | |||
} | |||
function renderGuest(box) { | |||
box.innerHTML = [ | |||
'<div class="rail-account-title">账户</div>', | |||
'<p class="rail-account-guest">您的身份是访客</p>', | |||
'<div class="rail-account-actions">', | |||
' <a href="' + mw.util.getUrl('Special:UserLogin') + '" class="rail-action-button">登录</a>', | |||
' <a href="' + mw.util.getUrl('Special:CreateAccount') + '" class="rail-action-button">创建账户</a>', | |||
'</div>' | |||
].join(''); | |||
} | |||
function renderUser(box) { | |||
var userName = mw.config.get('wgUserName'); | |||
var groups = mw.config.get('wgUserGroups') || []; | |||
var editCount = mw.config.get('wgUserEditCount'); | |||
var regTime = mw.config.get('wgUserRegistration'); | |||
box.innerHTML = [ | |||
'<div class="rail-account-title">账户:' + userName + '</div>', | |||
'<div class="rail-account-groups">权限组:' + getGroupHtml(groups) + '</div>', | |||
'<div class="rail-account-actions">', | |||
' <a href="' + mw.util.getUrl('Special:Contributions/' + userName) + '" class="rail-action-button">贡献</a>', | |||
' <a href="' + mw.util.getUrl('Special:Watchlist') + '" class="rail-action-button">监视列表</a>', | |||
' <a href="' + mw.util.getUrl('User talk:' + userName) + '" class="rail-action-button">用户讨论</a>', | |||
'</div>', | |||
'<div class="rail-account-stats">', | |||
' <span>编辑数:' + (editCount != null ? editCount : '—') + '</span>', | |||
' <span>加入时间:' + formatDate(regTime) + '</span>', | |||
'</div>', | |||
'<div class="rail-account-logout">', | |||
' <a href="' + mw.util.getUrl('Special:UserLogout') + '" class="rail-action-button rail-action-button--danger">退出登录</a>', | |||
'</div>' | |||
].join(''); | |||
} | |||
function fillAccountInfo() { | |||
var box = document.getElementById('rail-account-info'); | |||
if (!box || box.dataset.filled === '1') return; | |||
box.dataset.filled = '1'; | |||
if (mw.config.get('wgUserName')) { | |||
renderUser(box); | |||
} else { | |||
renderGuest(box); | |||
} | |||
} | |||
function observeRailPanel() { | |||
var target = document.getElementById('rail-panel-content'); | |||
if (!target) return; | |||
var observer = new MutationObserver(fillAccountInfo); | |||
observer.observe(target, { childList: true, subtree: true }); | |||
fillAccountInfo(); | |||
} | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', observeRailPanel); | |||
} else { | |||
observeRailPanel(); | |||
} | |||
})(); | |||
/* ===== 在 rail 面板中触发 Citizen 偏好设置面板 ===== */ | |||
(function () { | |||
function bindRailPreferences() { | |||
var trigger = document.getElementById('rail-open-preferences'); | |||
if (!trigger || trigger.dataset.bound === '1') return; | |||
trigger.dataset.bound = '1'; | |||
trigger.addEventListener('click', function () { | |||
if (window.Rail && typeof Rail.close === 'function') { | |||
Rail.close(); | |||
} | |||
var details = document.getElementById('citizen-preferences-details'); | |||
if (details) { | |||
details.open = true; | |||
} | |||
document.body.classList.add('citizen-preferences-shown'); | |||
// 插入关闭按钮(只插一次) | |||
var card = document.getElementById('citizen-preferences-dropdown__card'); | |||
if (card && !card.querySelector('.citizen-preferences-close')) { | |||
var closeBtn = document.createElement('button'); | |||
closeBtn.className = 'citizen-preferences-close'; | |||
closeBtn.type = 'button'; | |||
closeBtn.textContent = '×'; | |||
closeBtn.setAttribute('aria-label', '关闭'); | |||
closeBtn.addEventListener('click', function () { | |||
document.body.classList.remove('citizen-preferences-shown'); | |||
var d = document.getElementById('citizen-preferences-details'); | |||
if (d) d.open = false; | |||
}); | |||
card.insertBefore(closeBtn, card.firstChild); | |||
} | |||
}); | |||
} | |||
// 点击面板外部关闭 | |||
document.addEventListener('click', function (e) { | |||
if (!document.body.classList.contains('citizen-preferences-shown')) return; | |||
var card = document.getElementById('citizen-preferences-dropdown__card'); | |||
var trigger = document.getElementById('rail-open-preferences'); | |||
if (!card) return; | |||
if (card.contains(e.target)) return; | |||
if (trigger && trigger.contains(e.target)) return; | |||
document.body.classList.remove('citizen-preferences-shown'); | |||
var details = document.getElementById('citizen-preferences-details'); | |||
if (details) details.open = false; | |||
}); | |||
// Esc 关闭 | |||
document.addEventListener('keydown', function (e) { | |||
if (e.key !== 'Escape') return; | |||
if (!document.body.classList.contains('citizen-preferences-shown')) return; | |||
document.body.classList.remove('citizen-preferences-shown'); | |||
var details = document.getElementById('citizen-preferences-details'); | |||
if (details) details.open = false; | |||
}); | |||
var observer = new MutationObserver(bindRailPreferences); | |||
observer.observe(document.body, { childList: true, subtree: true }); | |||
bindRailPreferences(); | |||
})(); | |||
/* ===== 侧边工具栏 Rail 管理器 ===== */ | /* ===== 侧边工具栏 Rail 管理器 ===== */ | ||
/* ===== 侧边工具栏 Rail 管理器 ===== */ | /* ===== 侧边工具栏 Rail 管理器 ===== */ | ||
/* ===== 首页轮播图 ===== */ | /* ===== 首页轮播图 ===== */ | ||
2026年9月27日 (日) 23:40的最新版本
/* ===== 侧边工具栏 Rail 管理器 ===== */
/* ===== 侧边工具栏 Rail 管理器 ===== */
window.Rail = (function () {
var items = [];
var activeId = null;
var panelEl, overlayEl, buttonsEl, contentEl, titleEl;
function ensureDOM() {
if (document.getElementById('rail-container')) return;
buttonsEl = document.createElement('div');
buttonsEl.id = 'rail-container';
buttonsEl.className = 'rail-container';
overlayEl = document.createElement('div');
overlayEl.id = 'rail-overlay';
overlayEl.className = 'rail-overlay';
panelEl = document.createElement('aside');
panelEl.id = 'rail-panel';
panelEl.className = 'rail-panel';
panelEl.innerHTML = [
'<div class="rail-panel__header">',
' <span class="rail-panel__title"></span>',
' <button class="rail-panel__close" type="button" aria-label="关闭">×</button>',
'</div>',
'<div class="rail-panel__content" id="rail-panel-content"></div>'
].join('');
titleEl = panelEl.querySelector('.rail-panel__title');
contentEl = panelEl.querySelector('#rail-panel-content');
document.body.appendChild(overlayEl);
document.body.appendChild(panelEl);
document.body.appendChild(buttonsEl);
panelEl.querySelector('.rail-panel__close').addEventListener('click', close);
overlayEl.addEventListener('click', close);
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') close();
});
}
function createButton(item) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'rail-button fx-swap fx-pulse'; // 呼吸
btn.setAttribute('data-rail-id', item.id);
btn.setAttribute('aria-label', item.label);
btn.setAttribute('title', item.label);
var iconDefault = item.icon || '●';
var iconActive = item.iconActive || iconDefault;
btn.innerHTML =
'<span class="fx-swap__a rail-button__icon fx-text-pulse">' + iconDefault + '</span>' +
'<span class="fx-swap__b rail-button__icon fx-text-pulse">' + iconActive + '</span>';
btn.addEventListener('click', function () {
if (activeId === item.id) {
close();
} else {
open(item);
}
});
return btn;
}
function highlightButtons() {
var all = buttonsEl.querySelectorAll('.rail-button');
for (var i = 0; i < all.length; i++) {
var id = all[i].getAttribute('data-rail-id');
all[i].classList.toggle('is-active', id === activeId);
}
}
function open(item) {
activeId = item.id;
titleEl.textContent = item.label;
contentEl.innerHTML = '<p class="rail-panel__loading">加载中…</p>';
highlightButtons();
document.body.classList.add('rail-open');
if (item.html) {
contentEl.innerHTML = item.html;
return;
}
if (item.template) {
var api = new mw.Api();
api.get({
action: 'parse',
page: item.template,
prop: 'text',
formatversion: '2'
}).done(function (data) {
if (activeId !== item.id) return; // 用户切换了按钮
if (data && data.parse && data.parse.text) {
contentEl.innerHTML = data.parse.text;
} else {
contentEl.innerHTML = '<p>加载失败。</p>';
}
}).fail(function () {
if (activeId !== item.id) return;
contentEl.innerHTML = '<p>加载失败。</p>';
});
} else {
contentEl.innerHTML = '<p>未配置内容。</p>';
}
}
function close() {
activeId = null;
document.body.classList.remove('rail-open');
if (buttonsEl) highlightButtons();
}
function init() {
ensureDOM();
buttonsEl.innerHTML = '';
items.forEach(function (item) {
buttonsEl.appendChild(createButton(item));
});
}
return {
register: function (id, options) {
options = options || {};
items.push({
id: id,
icon: options.icon || '●',
label: options.label || id,
html: options.html || null,
template: options.template || null
});
},
init: init,
open: open,
close: close
};
})();
/* ===== 在这里注册工具栏按钮 ===== */
Rail.register('navigation', {
icon: '☰',
label: '导航',
template: 'Template:工具栏导航'
});
Rail.register('claim', {
icon: '!',
label: '公告',
template: 'Template:工具栏公告'
});
Rail.register('help', {
icon: '?',
label: '帮助',
template: 'Template:工具栏帮助'
});
Rail.register('settings', {
icon: '⚙',
label: '设置',
template: 'Template:工具栏设置'
});
/* ===== 初始化 ===== */
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function () { Rail.init(); });
} else {
Rail.init();
}
/* ===== 工具栏账户信息动态填充 ===== */
(function () {
var HIDDEN_GROUPS = ['*', 'user', 'autoconfirmed', 'emailconfirmed'];
var GROUP_NAME_FALLBACK = {
'sysop': '策展人',
'bureaucrat': '行政员',
'interface-admin': '织界者',
'interface_admin': '织界者',
'user': '旅人',
'autoconfirmed': '自动确认用户'
};
function formatDate(ms) {
if (!ms) return '—';
var d = new Date(ms);
return d.getFullYear() + '-' +
String(d.getMonth() + 1).padStart(2, '0') + '-' +
String(d.getDate()).padStart(2, '0');
}
function isMissing(msg) {
if (!msg) return true;
// 消息未加载时 MediaWiki 会返回 ⧼key⧽ 形式
return msg.indexOf('\u29FC') !== -1 || msg.indexOf('⧼') !== -1;
}
function getGroupName(g) {
var txt = mw.msg('group-' + g + '-member');
if (!isMissing(txt)) return txt;
return GROUP_NAME_FALLBACK[g] || g;
}
function getGroupUrl(g) {
var page = mw.msg('grouppage-' + g);
if (!isMissing(page)) {
return mw.util.getUrl(page);
}
return mw.util.getUrl('Anachronism:' + getGroupName(g));
}
function getGroupHtml(groups) {
var visible = groups.filter(function (g) {
return HIDDEN_GROUPS.indexOf(g) === -1;
});
if (!visible.length) return '无';
return visible.map(function (g) {
var name = getGroupName(g);
var url = getGroupUrl(g);
return '<a href="' + url + '" class="rail-group-tag">' + name + '</a>';
}).join('');
}
function renderGuest(box) {
box.innerHTML = [
'<div class="rail-account-title">账户</div>',
'<p class="rail-account-guest">您的身份是访客</p>',
'<div class="rail-account-actions">',
' <a href="' + mw.util.getUrl('Special:UserLogin') + '" class="rail-action-button">登录</a>',
' <a href="' + mw.util.getUrl('Special:CreateAccount') + '" class="rail-action-button">创建账户</a>',
'</div>'
].join('');
}
function renderUser(box) {
var userName = mw.config.get('wgUserName');
var groups = mw.config.get('wgUserGroups') || [];
var editCount = mw.config.get('wgUserEditCount');
var regTime = mw.config.get('wgUserRegistration');
box.innerHTML = [
'<div class="rail-account-title">账户:' + userName + '</div>',
'<div class="rail-account-groups">权限组:' + getGroupHtml(groups) + '</div>',
'<div class="rail-account-actions">',
' <a href="' + mw.util.getUrl('Special:Contributions/' + userName) + '" class="rail-action-button">贡献</a>',
' <a href="' + mw.util.getUrl('Special:Watchlist') + '" class="rail-action-button">监视列表</a>',
' <a href="' + mw.util.getUrl('User talk:' + userName) + '" class="rail-action-button">用户讨论</a>',
'</div>',
'<div class="rail-account-stats">',
' <span>编辑数:' + (editCount != null ? editCount : '—') + '</span>',
' <span>加入时间:' + formatDate(regTime) + '</span>',
'</div>',
'<div class="rail-account-logout">',
' <a href="' + mw.util.getUrl('Special:UserLogout') + '" class="rail-action-button rail-action-button--danger">退出登录</a>',
'</div>'
].join('');
}
function fillAccountInfo() {
var box = document.getElementById('rail-account-info');
if (!box || box.dataset.filled === '1') return;
box.dataset.filled = '1';
if (mw.config.get('wgUserName')) {
renderUser(box);
} else {
renderGuest(box);
}
}
function observeRailPanel() {
var target = document.getElementById('rail-panel-content');
if (!target) return;
var observer = new MutationObserver(fillAccountInfo);
observer.observe(target, { childList: true, subtree: true });
fillAccountInfo();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', observeRailPanel);
} else {
observeRailPanel();
}
})();
/* ===== 在 rail 面板中触发 Citizen 偏好设置面板 ===== */
(function () {
function bindRailPreferences() {
var trigger = document.getElementById('rail-open-preferences');
if (!trigger || trigger.dataset.bound === '1') return;
trigger.dataset.bound = '1';
trigger.addEventListener('click', function () {
if (window.Rail && typeof Rail.close === 'function') {
Rail.close();
}
var details = document.getElementById('citizen-preferences-details');
if (details) {
details.open = true;
}
document.body.classList.add('citizen-preferences-shown');
// 插入关闭按钮(只插一次)
var card = document.getElementById('citizen-preferences-dropdown__card');
if (card && !card.querySelector('.citizen-preferences-close')) {
var closeBtn = document.createElement('button');
closeBtn.className = 'citizen-preferences-close';
closeBtn.type = 'button';
closeBtn.textContent = '×';
closeBtn.setAttribute('aria-label', '关闭');
closeBtn.addEventListener('click', function () {
document.body.classList.remove('citizen-preferences-shown');
var d = document.getElementById('citizen-preferences-details');
if (d) d.open = false;
});
card.insertBefore(closeBtn, card.firstChild);
}
});
}
// 点击面板外部关闭
document.addEventListener('click', function (e) {
if (!document.body.classList.contains('citizen-preferences-shown')) return;
var card = document.getElementById('citizen-preferences-dropdown__card');
var trigger = document.getElementById('rail-open-preferences');
if (!card) return;
if (card.contains(e.target)) return;
if (trigger && trigger.contains(e.target)) return;
document.body.classList.remove('citizen-preferences-shown');
var details = document.getElementById('citizen-preferences-details');
if (details) details.open = false;
});
// Esc 关闭
document.addEventListener('keydown', function (e) {
if (e.key !== 'Escape') return;
if (!document.body.classList.contains('citizen-preferences-shown')) return;
document.body.classList.remove('citizen-preferences-shown');
var details = document.getElementById('citizen-preferences-details');
if (details) details.open = false;
});
var observer = new MutationObserver(bindRailPreferences);
observer.observe(document.body, { childList: true, subtree: true });
bindRailPreferences();
})();
/* ===== 侧边工具栏 Rail 管理器 ===== */
/* ===== 侧边工具栏 Rail 管理器 ===== */
/* ===== 首页轮播图 ===== */
(function () {
function initHomeCarousel() {
var carousel = document.getElementById('home-carousel');
if (!carousel) return;
var items = carousel.querySelectorAll('.home-carousel-item');
var dotsBox = document.getElementById('home-carousel-dots');
var prevBtn = document.getElementById('home-carousel-prev');
var nextBtn = document.getElementById('home-carousel-next');
if (!items.length || !dotsBox || !prevBtn || !nextBtn) return;
var current = 0;
var timer = null;
items.forEach(function (_, i) {
var dot = document.createElement('span');
dot.className = 'home-carousel-dot' + (i === 0 ? ' active' : '');
dot.addEventListener('click', function () { goTo(i); });
dotsBox.appendChild(dot);
});
var dots = dotsBox.querySelectorAll('.home-carousel-dot');
function goTo(index) {
items[current].classList.remove('active');
dots[current].classList.remove('active');
current = (index + items.length) % items.length;
items[current].classList.add('active');
dots[current].classList.add('active');
}
function next() { goTo(current + 1); }
function prev() { goTo(current - 1); }
function startAuto() {
stopAuto();
timer = setInterval(next, 5000);
}
function stopAuto() {
if (timer) { clearInterval(timer); timer = null; }
}
nextBtn.addEventListener('click', function () { next(); startAuto(); });
prevBtn.addEventListener('click', function () { prev(); startAuto(); });
carousel.addEventListener('mouseenter', stopAuto);
carousel.addEventListener('mouseleave', startAuto);
startAuto();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initHomeCarousel);
} else {
initHomeCarousel();
}
})();