MediaWiki:Gadget-site-js.js
MediaWiki界面页面
更多操作
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
/**********************
* 模块 0:优先加载 vConsole(立即执行)
**********************/
(function() {
// 调试控制开关(生产环境可设置为 false)
const DEBUG_MODE = true;
if (DEBUG_MODE && !window.localStorage.getItem('disableVConsole')) {
console.log('[Init] 正在加载调试控制台...');
const vConsoleScript = document.createElement('script');
vConsoleScript.src = 'https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js';
vConsoleScript.integrity = 'sha384-+EmbrakM5WQd6BSpR2GQKJqH15zFVECXZQ5qPj4hEtZj0jFz4iF2PnmFUTTvWH+1';
vConsoleScript.crossOrigin = 'anonymous';
vConsoleScript.onload = function() {
try {
// 初始化 vConsole 并配置
const vConsole = new window.VConsole({
theme: 'dark',
onReady: function() {
console.log('[vConsole] 调试面板已就绪');
// 捕获未处理的Promise错误
window.addEventListener('unhandledrejection', event => {
console.error('[未处理的Promise错误]', event.reason);
});
}
});
// 全局错误监听(捕获语法错误之外的运行时错误)
window.addEventListener('error', function(event) {
console.error(
'[全局错误]',
`类型: ${event.error?.name || 'Error'}\n`,
`信息: ${event.message}\n`,
`文件: ${event.filename}\n`,
`行号: ${event.lineno}:${event.colno}`
);
});
} catch (e) {
console.error('[vConsole] 初始化失败', e);
}
};
vConsoleScript.onerror = function() {
console.warn('[vConsole] 加载失败,尝试备用CDN...');
loadFallbackVConsole();
};
document.head.insertBefore(vConsoleScript, document.head.firstChild);
// 备用CDN加载
function loadFallbackVConsole() {
const fallbackScript = document.createElement('script');
fallbackScript.src = 'https://cdn.jsdelivr.net/npm/vconsole@3.15.1/dist/vconsole.min.js';
fallbackScript.onload = vConsoleScript.onload;
fallbackScript.onerror = function() {
console.error('[vConsole] 所有CDN加载失败,将无法显示控制台');
};
document.head.appendChild(fallbackScript);
}
}
})();
/**********************
* 模块 1:CSS 变量 RGB 转换(安全封装)
**********************/
;(function() {
try {
const ColorUtil = (function () {
const OBS_VAR = '--background-color-base';
let cachedRgb = '';
function parseHex(hex) {
try {
hex = hex.replace(/^#/, '');
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
if (hex.length !== 6) return '0, 0, 0';
const [r, g, b] = [0, 2, 4].map(i => parseInt(hex.substring(i, i + 2), 16) || 0);
return `${r}, ${g}, ${b}`;
} catch (e) {
console.error('[ColorUtil] 颜色解析失败', e);
return '0, 0, 0';
}
}
function safeUpdate() {
try {
const root = document.documentElement;
const hex = getComputedStyle(root).getPropertyValue(OBS_VAR).trim();
const rgb = parseHex(hex || '#000');
if (rgb !== cachedRgb) {
root.style.setProperty('--background-color-rgb', rgb);
cachedRgb = rgb;
console.log('[ColorUtil] RGB值已更新:', rgb);
}
} catch (e) {
console.warn('[ColorUtil] 更新失败', e);
}
}
function init() {
try {
safeUpdate();
const mo = new MutationObserver(safeUpdate);
mo.observe(document.documentElement, {
attributes: true,
attributeFilter: ['style']
});
return {
destroy: () => mo.disconnect(),
forceUpdate: safeUpdate
};
} catch (e) {
console.error('[ColorUtil] 初始化失败', e);
return { destroy: () => {}, forceUpdate: () => {} };
}
}
return init();
})();
// 暴露到全局(可选)
window.ColorUtil = ColorUtil;
} catch (e) {
console.error('[模块1] 初始化异常', e);
}
})();
/**********************
* 模块 2:上传页引导弹窗(安全封装)
**********************/
;(function() {
try {
if (!window.mw || !mw.config || mw.config.get('wgCanonicalSpecialPageName') !== 'Upload') {
return;
}
const KEY = 'uploadPreference_v2';
const pref = mw.storage.get(KEY);
if (pref === 'wizard') {
window.location.href = mw.util.getUrl('Special:UploadWizard');
return;
}
if (pref === 'classic') return;
function initDialog(event) {
// 确保只触发一次
if (event && event.target.closest('#mw-content-text')) {
document.removeEventListener('click', initDialog);
showDialog();
}
}
function showDialog() {
mw.loader.using(['mediawiki.util', 'mediawiki.storage', 'oojs-ui-core', 'oojs-ui-widgets'])
.then(() => {
try {
class UploadDialog extends OO.ui.ProcessDialog {
static static = {
name: 'UploadDialog',
title: '请选择上传方式',
actions: [
{
action: 'classic',
label: '❌ 使用传统上传方式',
flags: ['safe']
}
]
};
initialize() {
super.initialize();
const panel = new OO.ui.PanelLayout({ padded: true });
this._remember = new OO.ui.CheckboxInputWidget();
const rememberField = new OO.ui.FieldLayout(this._remember, {
label: '记住我的选择',
align: 'inline'
});
const wizardBtn = new OO.ui.ButtonWidget({
label: '✅ 使用上传向导(推荐)',
flags: ['primary', 'progressive'],
href: mw.util.getUrl('Special:UploadWizard'),
target: '_self'
});
wizardBtn.on('click', () => {
if (this._remember.isSelected()) {
mw.storage.set(KEY, 'wizard');
}
});
panel.$element.append(
$('<p>').text('请选择上传方式:'),
$('<div>').css('margin', '10px 0').append(wizardBtn.$element),
$('<div>').append(rememberField.$element)
);
this.$body.append(panel.$element);
}
getActionProcess(action) {
if (action === 'classic') {
return new OO.ui.Process(() => {
if (this._remember.isSelected()) {
mw.storage.set(KEY, 'classic');
}
this.close();
});
}
return super.getActionProcess(action);
}
}
const windowManager = new OO.ui.WindowManager();
document.body.appendChild(windowManager.$element);
windowManager.addWindows([new UploadDialog()]);
windowManager.openWindow('UploadDialog');
} catch (e) {
console.error('[UploadDialog] 创建失败', e);
// 降级处理:直接跳转
window.location.href = mw.util.getUrl('Special:UploadWizard');
}
})
.catch(e => {
console.error('[OOUI] 加载失败', e);
});
}
// 延迟绑定事件,避免阻塞
setTimeout(() => {
document.addEventListener('click', initDialog, { once: true });
}, 500);
} catch (e) {
console.error('[模块2] 初始化异常', e);
}
})();
/**********************
* 模块 3:全局加载指示器(安全封装)
**********************/
;(function() {
try {
const LoadingIndicator = (function () {
// 配置项(可自定义)
const config = {
imageUrl: 'https://example.com/loading-spinner.svg',
fallbackImage: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMDA3M2JhIiBzdHJva2Utd2lkdGg9IjgiIGZpbGw9Im5vbmUiIHN0cm9rZS1kYXNoYXJyYXk9IjYyLjgzIDYyLjgzIiB0cmFuc2Zvcm09InJvdGF0ZSgzNjAgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iMXMiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiLz48L2NpcmNsZT48L3N2Zz4=',
size: '64px',
position: { bottom: '20px', right: '20px' },
timeout: 15000,
animation: { fadeIn: 300, fadeOut: 500 },
zIndex: 99999
};
let instance = null;
let timeoutId = null;
let startTime = 0;
function createLoader() {
const loader = document.createElement('div');
loader.className = 'global-loader';
loader.setAttribute('aria-live', 'polite');
Object.assign(loader.style, {
position: 'fixed',
width: config.size,
height: config.size,
bottom: config.position.bottom,
right: config.position.right,
opacity: '0',
transition: `opacity ${config.animation.fadeIn}ms ease-out`,
zIndex: config.zIndex,
pointerEvents: 'none',
backgroundImage: `url(${config.imageUrl})`,
backgroundSize: 'contain',
backgroundRepeat: 'no-repeat'
});
// 备用图片处理
loader.onerror = function() {
this.style.backgroundImage = `url(${config.fallbackImage})`;
};
return loader;
}
function show() {
if (instance) return resetTimer();
startTime = Date.now();
instance = createLoader();
document.body.appendChild(instance);
requestAnimationFrame(() => {
instance.style.opacity = '1';
});
resetTimer();
addEventListeners();
}
function hide() {
if (!instance) return;
const elapsed = Date.now() - startTime;
const minDisplayTime = 500; // 最少显示500ms
const delay = Math.max(minDisplayTime - elapsed, 0);
clearTimeout(timeoutId);
setTimeout(() => {
instance.style.opacity = '0';
instance.setAttribute('aria-busy', 'false');
setTimeout(() => {
if (instance && instance.parentNode) {
instance.parentNode.removeChild(instance);
}
instance = null;
removeEventListeners();
}, config.animation.fadeOut);
}, delay);
}
function resetTimer() {
clearTimeout(timeoutId);
timeoutId = setTimeout(hide, config.timeout);
}
function handleOnline() {
console.log('[Loader] 网络恢复');
}
function handleOffline() {
console.warn('[Loader] 网络断开');
hide();
}
function addEventListeners() {
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
}
function removeEventListeners() {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
}
// 自动绑定生命周期事件
document.addEventListener('DOMContentLoaded', show);
window.addEventListener('load', hide);
// 页面离开时清理
window.addEventListener('beforeunload', () => {
if (instance) {
hide();
}
});
return {
show,
hide,
updateConfig: (newConfig) => {
Object.assign(config, newConfig);
}
};
})();
// 暴露到全局(可选)
window.LoadingIndicator = LoadingIndicator;
} catch (e) {
console.error('[模块3] 初始化异常', e);
}
})();
/**********************
* 错误兜底处理(最后执行)
**********************/
;(function() {
// 如果vConsole未加载,显示简化错误提示
if (!window.vConsole) {
window.addEventListener('error', function(event) {
console.error('页面错误:', event.error);
const errorBox = document.createElement('div');
errorBox.style.cssText = `
position: fixed;
bottom: 20px;
left: 20px;
right: 20px;
padding: 15px;
background: #ffebee;
border: 2px solid #f44336;
border-radius: 5px;
z-index: 99999;
font-family: sans-serif;
`;
errorBox.innerHTML = `
<h3 style="margin-top:0;color:#d32f2f">页面错误</h3>
<p>${event.message}</p>
<button onclick="this.parentNode.remove()"
style="background:#f44336;color:white;border:none;padding:5px 10px;border-radius:3px">
关闭
</button>
`;
document.body.appendChild(errorBox);
});
}
})();