MediaWiki:Gadget-site-js.js:修订间差异
MediaWiki界面页面
更多操作
![]() OctoberSama(留言 | 贡献) (OctoberSama移动页面MediaWiki:Gadget-TheCommon.js至MediaWiki:Gadget-site-js.js:快速重命名 → MediaWiki:Gadget-site-js.js) 小 |
|||
第1行: | 第1行: | ||
const | /********************** | ||
const | * 模块 1:CSS 变量 RGB 转换 | ||
**********************/ | |||
const ColorUtil = (function () { | |||
const OBS_VAR = '--background-color-base'; | |||
let cachedRgb = ''; | |||
function | function parseHex(hex) { | ||
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}`; | |||
} | } | ||
const | function update() { | ||
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 updated:', rgb); | |||
} | |||
} | |||
update(); | |||
const | const mo = new MutationObserver(update); | ||
document.documentElement | mo.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] }); | ||
); | |||
return { | |||
destroy() { mo.disconnect(); }, | |||
forceUpdate: update | |||
}; | |||
})(); | |||
// | /********************** | ||
* 模块 2:上传页引导弹窗(按需加载 OOUI) | |||
**********************/ | |||
(function (mw) { | |||
if (!mw || mw.config.get('wgCanonicalSpecialPageName') !== 'Upload') return; | |||
const KEY = 'uploadPreference'; | |||
const pref = mw.storage.get(KEY); | |||
if (pref === 'wizard') return window.location = mw.util.getUrl('Special:UploadWizard'); | |||
if (pref === 'classic') return; | |||
function initDialog() { | |||
document.removeEventListener('click', initDialog); | |||
mw.loader.using(['mediawiki.util', 'mediawiki.storage', 'oojs-ui-core', 'oojs-ui-widgets']) | |||
.then(() => { | |||
class UploadDialog extends OO.ui.ProcessDialog { | |||
static name = 'UploadDialog'; | |||
static title = '请选择上传方式'; | |||
static 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>').append(wizardBtn.$element), | |||
$('<div>').append(rememberField.$element) | |||
); | |||
this.$body.append(panel.$element); | |||
} | |||
getActionProcess(action) { | |||
if (action === 'classic') { | |||
return; | if (this._remember.isSelected()) mw.storage.set(KEY, 'classic'); | ||
return new OO.ui.Process(() => this.close()); | |||
} | |||
return super.getActionProcess(action); | |||
} | |||
} | } | ||
const wm = new OO.ui.WindowManager(); | |||
document.body.appendChild(wm.$element); | |||
wm.addWindows([new UploadDialog()]); | |||
wm.openWindow('UploadDialog'); | |||
}); | |||
} | |||
document.addEventListener('click', initDialog); | |||
})(window.mw); | |||
/********************** | |||
* 模块 3:全局加载指示器 | |||
**********************/ | |||
const LoadingIndicator = (function () { | |||
let indicator = null; | |||
let timerId = null; | |||
let startTs = 0; | |||
const minDisplay = 200; | |||
const cfg = { | |||
imageUrl: 'https://wiki.ottohub.cn/images/0/02/Loading.png', | |||
size: '64px', bottom: '20px', right: '20px', | |||
timeout: 15000, fade: 300, zIndex: 99999 | |||
}; | |||
function show(reason = '') { | |||
if (indicator) return resetTimeout(); | |||
startTs = Date.now(); | |||
indicator = document.createElement('div'); | |||
Object.assign(indicator.style, { | |||
position: 'fixed', width: cfg.size, height: cfg.size, | |||
bottom: cfg.bottom, right: cfg.right, | |||
opacity: '0', transition: `opacity ${cfg.fade}ms`, | |||
zIndex: cfg.zIndex, pointerEvents: 'none' | |||
}); | |||
indicator.setAttribute('aria-busy', 'true'); | |||
const img = new Image(); | |||
img.src = cfg.imageUrl; | |||
img.alt = '加载中'; | |||
img.style.width = '100%'; | |||
indicator.appendChild(img); | |||
document.body.appendChild(indicator); | |||
requestAnimationFrame(() => { indicator.style.opacity = '1'; }); | |||
resetTimeout(); | |||
} | |||
function hide(reason = '') { | |||
if (!indicator) return; | |||
const elapsed = Date.now() - startTs; | |||
const wait = Math.max(minDisplay - elapsed, 0); | |||
setTimeout(() => { | |||
indicator.style.opacity = '0'; | |||
indicator.setAttribute('aria-busy', 'false'); | |||
setTimeout(() => { | |||
indicator.remove(); | |||
indicator = null; | |||
}, cfg.fade); | |||
}, wait); | |||
clearTimeout(timerId); | |||
} | |||
function resetTimeout() { | |||
clearTimeout(timerId); | |||
timerId = setTimeout(() => hide('timeout'), cfg.timeout); | |||
} | |||
window.addEventListener('offline', () => { | |||
console.warn('[Loading] 离线,强制隐藏'); | |||
hide('offline'); | |||
}); | |||
window.addEventListener('online', () => { | |||
console.info('[Loading] 网络恢复'); | |||
}); | |||
document.addEventListener('DOMContentLoaded', () => show('DOMContentLoaded')); | |||
window.addEventListener('load', () => hide('load')); | |||
return { show, hide }; | |||
})(); | |||
2025年6月20日 (五) 20:44的版本
/**********************
* 模块 1:CSS 变量 RGB 转换
**********************/
const ColorUtil = (function () {
const OBS_VAR = '--background-color-base';
let cachedRgb = '';
function parseHex(hex) {
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}`;
}
function update() {
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 updated:', rgb);
}
}
update();
const mo = new MutationObserver(update);
mo.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });
return {
destroy() { mo.disconnect(); },
forceUpdate: update
};
})();
/**********************
* 模块 2:上传页引导弹窗(按需加载 OOUI)
**********************/
(function (mw) {
if (!mw || mw.config.get('wgCanonicalSpecialPageName') !== 'Upload') return;
const KEY = 'uploadPreference';
const pref = mw.storage.get(KEY);
if (pref === 'wizard') return window.location = mw.util.getUrl('Special:UploadWizard');
if (pref === 'classic') return;
function initDialog() {
document.removeEventListener('click', initDialog);
mw.loader.using(['mediawiki.util', 'mediawiki.storage', 'oojs-ui-core', 'oojs-ui-widgets'])
.then(() => {
class UploadDialog extends OO.ui.ProcessDialog {
static name = 'UploadDialog';
static title = '请选择上传方式';
static 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>').append(wizardBtn.$element),
$('<div>').append(rememberField.$element)
);
this.$body.append(panel.$element);
}
getActionProcess(action) {
if (action === 'classic') {
if (this._remember.isSelected()) mw.storage.set(KEY, 'classic');
return new OO.ui.Process(() => this.close());
}
return super.getActionProcess(action);
}
}
const wm = new OO.ui.WindowManager();
document.body.appendChild(wm.$element);
wm.addWindows([new UploadDialog()]);
wm.openWindow('UploadDialog');
});
}
document.addEventListener('click', initDialog);
})(window.mw);
/**********************
* 模块 3:全局加载指示器
**********************/
const LoadingIndicator = (function () {
let indicator = null;
let timerId = null;
let startTs = 0;
const minDisplay = 200;
const cfg = {
imageUrl: 'https://wiki.ottohub.cn/images/0/02/Loading.png',
size: '64px', bottom: '20px', right: '20px',
timeout: 15000, fade: 300, zIndex: 99999
};
function show(reason = '') {
if (indicator) return resetTimeout();
startTs = Date.now();
indicator = document.createElement('div');
Object.assign(indicator.style, {
position: 'fixed', width: cfg.size, height: cfg.size,
bottom: cfg.bottom, right: cfg.right,
opacity: '0', transition: `opacity ${cfg.fade}ms`,
zIndex: cfg.zIndex, pointerEvents: 'none'
});
indicator.setAttribute('aria-busy', 'true');
const img = new Image();
img.src = cfg.imageUrl;
img.alt = '加载中';
img.style.width = '100%';
indicator.appendChild(img);
document.body.appendChild(indicator);
requestAnimationFrame(() => { indicator.style.opacity = '1'; });
resetTimeout();
}
function hide(reason = '') {
if (!indicator) return;
const elapsed = Date.now() - startTs;
const wait = Math.max(minDisplay - elapsed, 0);
setTimeout(() => {
indicator.style.opacity = '0';
indicator.setAttribute('aria-busy', 'false');
setTimeout(() => {
indicator.remove();
indicator = null;
}, cfg.fade);
}, wait);
clearTimeout(timerId);
}
function resetTimeout() {
clearTimeout(timerId);
timerId = setTimeout(() => hide('timeout'), cfg.timeout);
}
window.addEventListener('offline', () => {
console.warn('[Loading] 离线,强制隐藏');
hide('offline');
});
window.addEventListener('online', () => {
console.info('[Loading] 网络恢复');
});
document.addEventListener('DOMContentLoaded', () => show('DOMContentLoaded'));
window.addEventListener('load', () => hide('load'));
return { show, hide };
})();