MediaWiki:Gadget-site-js.js:修订间差异
MediaWiki界面页面
更多操作
第125行: | 第125行: | ||
}); | }); | ||
//--- | //--- | ||
// | // IIFE (Immediately Invoked Function Expression) | ||
(function() { | |||
// | // --- 1. 尽早执行和检查重复 --- | ||
if (window. | // 如果已初始化,不再执行任何操作 | ||
window. | if (window.__loadingIndicatorActive) { // 使用一个更明确的标志,表示 loading 当前是否“活跃” | ||
// | return; | ||
var loadingImgUrl = 'https://wiki.ottohub.cn/images/0/02/Loading.png'; | } | ||
window.__loadingIndicatorActive = true; // 标记 loading 开始 | |||
var | |||
// --- 2. 尽早创建和插入 Loading DOM --- | |||
var loadingImgUrl = 'https://wiki.ottohub.cn/images/0/02/Loading.png'; | |||
var loadingDiv = document.createElement('div'); | |||
loadingDiv.id = 'loadingIndicator'; | |||
loadingDiv.style.position = 'fixed'; | |||
loadingDiv.style.bottom = '20px'; | |||
loadingDiv.style.right = '20px'; | |||
loadingDiv.style.width = '256px'; // 强制大小,如果图片本身就是这个尺寸,可以省略 | |||
' | loadingDiv.style.height = '256px'; | ||
loadingDiv.style.zIndex = '99999'; // 更高的 z-index 确保在最上层 | |||
loadingDiv.style.pointerEvents = 'none'; // 允许点击穿透 | |||
// loadingDiv.style.display = 'none'; // 先隐藏,通过 fadeIn 显示,可选 | |||
var img = document.createElement('img'); | |||
img.src = loadingImgUrl; | |||
img.alt = 'Loading...'; | |||
img.style.width = '100%'; | |||
img.style.height = '100%'; | |||
img.style.objectFit = 'contain'; | |||
loadingDiv.appendChild(img); | |||
// 确保 body 存在。如果此脚本在 <head> 中且没有 defer/async, | |||
// document.body 可能为 null。最安全的方式是把脚本放在 </body> 前。 | |||
// 或者,如果必须在 head 中,则等待 DOMContentLoaded 附加。 | |||
// 但为了“尽早显示”,我们这里会尝试立即附加,并提供回退。 | |||
function appendLoadingIcon() { | |||
if (document.body) { | |||
document.body.appendChild(loadingDiv); | |||
// 可选:使用 fadeIn 效果,如果上面设置了 display: 'none' | |||
// loadingDiv.style.display = 'block'; // 或者使用 jQuery fadeIn | |||
// $(loadingDiv).stop(true, true).fadeIn(100); // 如果你还想用 jQuery 做动画 | |||
} else { | |||
// 如果 body 还不存在,等待 DOMContentLoaded | |||
// 这意味着 loading 图标的显示会稍有延迟 | |||
document.addEventListener('DOMContentLoaded', function onReady() { | |||
if (document.body && !document.getElementById('loadingIndicator')) { // 再次检查,防止重复 | |||
document.body.appendChild(loadingDiv); | |||
// $(loadingDiv).stop(true, true).fadeIn(100); | |||
} | |||
document.removeEventListener('DOMContentLoaded', onReady); // 清理监听器 | |||
}); | |||
} | } | ||
} | } | ||
appendLoadingIcon(); // 尝试立即附加 | |||
); | |||
// --- 3. 移除逻辑 --- | |||
var removalTimeoutId = null; // 用于清除兜底 timeout | |||
function removeLoading(reason) { | function removeLoading(reason) { | ||
// 确保只移除一次,并清除兜底 timeout | |||
if (!window.__loadingIndicatorActive) return; // 如果已经移除了,则不操作 | |||
var el = document.getElementById('loadingIndicator'); | |||
if (el) { | |||
console.log(`[Common.js] 🔄 Removing loading. Reason: ${reason}`); | |||
// 使用原生 JS 实现 fadeOut 效果 (可选,或直接 remove) | |||
el.style.transition = 'opacity 0.3s ease-out'; | |||
el.style.opacity = '0'; | |||
setTimeout(function() { | |||
if (el.parentNode) { | |||
el.parentNode.removeChild(el); | |||
} | |||
console.log(`[Common.js] ✅ Loading removed. Reason: ${reason}`); | |||
}, 300); // 等待 fadeOut 动画完成 | |||
} else { | |||
// console.log(`[Common.js] ⚠️ Loading indicator already removed or not found when trying to remove. Reason: ${reason}`); | |||
} | |||
window.__loadingIndicatorActive = false; // 标记 loading 已移除 | |||
if (removalTimeoutId) { | |||
clearTimeout(removalTimeoutId); // 清除兜底的10秒timeout | |||
removalTimeoutId = null; | |||
} | |||
// 清理事件监听器,防止内存泄漏(对于只触发一次的事件,现代浏览器通常会自动处理,但显式移除更安全) | |||
window.removeEventListener('load', onWindowLoad); | |||
document.removeEventListener('DOMContentLoaded', onDOMContentLoaded); | |||
} | } | ||
// | // --- 4. 检查即时状态并设置事件监听 --- | ||
// | // 立即检查 document.readyState (处理缓存或极快加载) | ||
if (document.readyState === 'complete') { | |||
// 使用 requestAnimationFrame 或短 setTimeout 给浏览器一点点时间渲染 loading 图标 | |||
// 然后再移除,避免完全看不到。 | |||
}); | // 如果不希望看到闪烁,可以直接调用 removeLoading。 | ||
requestAnimationFrame(function() { // 或者 setTimeout(..., 0) | |||
removeLoading('document.readyState was already complete on script exec'); | |||
}); | |||
return; // 已经加载完成,不需要后续的监听器和 timeout | |||
} | |||
// | // 定义事件处理函数,以便之后可以移除它们 | ||
function onWindowLoad() { | |||
removeLoading('window.load'); | |||
} | |||
if (document.readyState === 'complete' | function onDOMContentLoaded() { | ||
if (document.readyState === 'complete' || document.readyState === 'interactive') { | |||
// DOMContentLoaded 之后,如果已经是 complete 或 interactive,也可以认为主要内容加载完成 | |||
// | // 使用 setTimeout 给一个微小的延迟,确保其他 DOMContentLoaded 任务有机会执行 | ||
setTimeout(function () { | setTimeout(function() { | ||
removeLoading('DOMContentLoaded (interactive/complete)'); | |||
}, 0); // 极短延迟 | |||
}, | |||
} | } | ||
} | // 如果不是 complete/interactive,则 window.load 仍然是主要依赖 | ||
} | |||
// | // 注册事件监听 | ||
setTimeout(function () { | window.addEventListener('load', onWindowLoad); | ||
document.addEventListener('DOMContentLoaded', onDOMContentLoaded); | |||
// 兜底策略:10 秒后强制关闭 | |||
removalTimeoutId = setTimeout(function() { | |||
removeLoading('timeout fallback (10s)'); | removeLoading('timeout fallback (10s)'); | ||
}, 10000); | }, 10000); | ||
}); | |||
})(); |
2025年5月11日 (日) 00:33的版本
//上传重定向
mw.loader.using(['mediawiki.util', 'oojs-ui', 'mediawiki.storage'], function () {
if (mw.config.get('wgCanonicalSpecialPageName') === 'Upload') {
const STORAGE_KEY = 'uploadPreference';
const savedChoice = mw.storage.get(STORAGE_KEY);
// 用户之前选择了上传向导,自动跳转
if (savedChoice === 'wizard') {
window.location.href = mw.util.getUrl('Special:UploadWizard');
return;
}
// 用户选择了继续使用传统上传,不再提示
if (savedChoice === 'classic') {
return;
}
// 否则弹出选择窗口
var windowManager = new OO.ui.WindowManager();
$(document.body).append(windowManager.$element);
function UploadDialog(config) {
UploadDialog.super.call(this, config);
}
OO.inheritClass(UploadDialog, OO.ui.ProcessDialog);
UploadDialog.static.name = 'UploadDialog';
UploadDialog.static.title = '请选择上传方式';
UploadDialog.static.actions = [
{
action: 'classic',
label: '❌ 使用传统上传方式(更快)',
flags: ['safe']
}
];
UploadDialog.prototype.initialize = function () {
UploadDialog.super.prototype.initialize.call(this);
var panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
// 创建“记住我的选择”复选框
var rememberCheckbox = new OO.ui.CheckboxInputWidget({ selected: false });
this._rememberCheckbox = rememberCheckbox;
var rememberField = new OO.ui.FieldLayout(rememberCheckbox, {
label: '记住我的选择,下次不再提示',
align: 'inline'
});
// 创建“上传向导”按钮(跳转链接形式)
var wizardButton = new OO.ui.ButtonWidget({
label: '✅ 使用上传向导(推荐)',
flags: ['primary', 'progressive'],
href: mw.util.getUrl('Special:UploadWizard'),
target: '_self',
framed: true
});
// 给按钮添加“记住选择”逻辑
wizardButton.on('click', () => {
if (rememberCheckbox.isSelected()) {
mw.storage.set(STORAGE_KEY, 'wizard');
}
});
// 拼接内容
panel.$element.append(
$('<p>').text('请选择你要使用的上传方式:'),
$('<div>').css({
'margin': '12px 0',
'background': '#f8f9fa',
'padding': '10px',
'border': '1px solid #ccc',
'border-radius': '5px'
}).append(
$('<strong>').text('✅ 上传向导(推荐)'),
$('<ul>').append(
$('<li>').text('支持多文件批量上传'),
$('<li>').text('显示上传进度条'),
$('<li>').text('信息填写更完整')
),
$('<div>').css('margin-top', '8px').append(wizardButton.$element)
),
$('<div>').css({
'margin-top': '15px',
'background': '#ffffff',
'padding': '10px',
'border': '1px dashed #bbb',
'border-radius': '5px'
}).append(
$('<strong>').text('⚡️ 传统上传方式'),
$('<ul>').append(
$('<li>').text('界面更简单'),
$('<li>').text('加载速度快'),
$('<li>').text('适合上传单个文件')
)
),
$('<div>').css('margin-top', '15px').append(rememberField.$element)
);
this.$body.append(panel.$element);
};
UploadDialog.prototype.getActionProcess = function (action) {
if (action === 'classic') {
if (this._rememberCheckbox.isSelected()) {
mw.storage.set(STORAGE_KEY, 'classic');
}
return new OO.ui.Process(() => this.close());
}
return UploadDialog.super.prototype.getActionProcess.call(this, action);
};
var dialog = new UploadDialog();
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
}
});
//---
// IIFE (Immediately Invoked Function Expression)
(function() {
// --- 1. 尽早执行和检查重复 ---
// 如果已初始化,不再执行任何操作
if (window.__loadingIndicatorActive) { // 使用一个更明确的标志,表示 loading 当前是否“活跃”
return;
}
window.__loadingIndicatorActive = true; // 标记 loading 开始
// --- 2. 尽早创建和插入 Loading DOM ---
var loadingImgUrl = 'https://wiki.ottohub.cn/images/0/02/Loading.png';
var loadingDiv = document.createElement('div');
loadingDiv.id = 'loadingIndicator';
loadingDiv.style.position = 'fixed';
loadingDiv.style.bottom = '20px';
loadingDiv.style.right = '20px';
loadingDiv.style.width = '256px'; // 强制大小,如果图片本身就是这个尺寸,可以省略
loadingDiv.style.height = '256px';
loadingDiv.style.zIndex = '99999'; // 更高的 z-index 确保在最上层
loadingDiv.style.pointerEvents = 'none'; // 允许点击穿透
// loadingDiv.style.display = 'none'; // 先隐藏,通过 fadeIn 显示,可选
var img = document.createElement('img');
img.src = loadingImgUrl;
img.alt = 'Loading...';
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'contain';
loadingDiv.appendChild(img);
// 确保 body 存在。如果此脚本在 <head> 中且没有 defer/async,
// document.body 可能为 null。最安全的方式是把脚本放在 </body> 前。
// 或者,如果必须在 head 中,则等待 DOMContentLoaded 附加。
// 但为了“尽早显示”,我们这里会尝试立即附加,并提供回退。
function appendLoadingIcon() {
if (document.body) {
document.body.appendChild(loadingDiv);
// 可选:使用 fadeIn 效果,如果上面设置了 display: 'none'
// loadingDiv.style.display = 'block'; // 或者使用 jQuery fadeIn
// $(loadingDiv).stop(true, true).fadeIn(100); // 如果你还想用 jQuery 做动画
} else {
// 如果 body 还不存在,等待 DOMContentLoaded
// 这意味着 loading 图标的显示会稍有延迟
document.addEventListener('DOMContentLoaded', function onReady() {
if (document.body && !document.getElementById('loadingIndicator')) { // 再次检查,防止重复
document.body.appendChild(loadingDiv);
// $(loadingDiv).stop(true, true).fadeIn(100);
}
document.removeEventListener('DOMContentLoaded', onReady); // 清理监听器
});
}
}
appendLoadingIcon(); // 尝试立即附加
// --- 3. 移除逻辑 ---
var removalTimeoutId = null; // 用于清除兜底 timeout
function removeLoading(reason) {
// 确保只移除一次,并清除兜底 timeout
if (!window.__loadingIndicatorActive) return; // 如果已经移除了,则不操作
var el = document.getElementById('loadingIndicator');
if (el) {
console.log(`[Common.js] 🔄 Removing loading. Reason: ${reason}`);
// 使用原生 JS 实现 fadeOut 效果 (可选,或直接 remove)
el.style.transition = 'opacity 0.3s ease-out';
el.style.opacity = '0';
setTimeout(function() {
if (el.parentNode) {
el.parentNode.removeChild(el);
}
console.log(`[Common.js] ✅ Loading removed. Reason: ${reason}`);
}, 300); // 等待 fadeOut 动画完成
} else {
// console.log(`[Common.js] ⚠️ Loading indicator already removed or not found when trying to remove. Reason: ${reason}`);
}
window.__loadingIndicatorActive = false; // 标记 loading 已移除
if (removalTimeoutId) {
clearTimeout(removalTimeoutId); // 清除兜底的10秒timeout
removalTimeoutId = null;
}
// 清理事件监听器,防止内存泄漏(对于只触发一次的事件,现代浏览器通常会自动处理,但显式移除更安全)
window.removeEventListener('load', onWindowLoad);
document.removeEventListener('DOMContentLoaded', onDOMContentLoaded);
}
// --- 4. 检查即时状态并设置事件监听 ---
// 立即检查 document.readyState (处理缓存或极快加载)
if (document.readyState === 'complete') {
// 使用 requestAnimationFrame 或短 setTimeout 给浏览器一点点时间渲染 loading 图标
// 然后再移除,避免完全看不到。
// 如果不希望看到闪烁,可以直接调用 removeLoading。
requestAnimationFrame(function() { // 或者 setTimeout(..., 0)
removeLoading('document.readyState was already complete on script exec');
});
return; // 已经加载完成,不需要后续的监听器和 timeout
}
// 定义事件处理函数,以便之后可以移除它们
function onWindowLoad() {
removeLoading('window.load');
}
function onDOMContentLoaded() {
if (document.readyState === 'complete' || document.readyState === 'interactive') {
// DOMContentLoaded 之后,如果已经是 complete 或 interactive,也可以认为主要内容加载完成
// 使用 setTimeout 给一个微小的延迟,确保其他 DOMContentLoaded 任务有机会执行
setTimeout(function() {
removeLoading('DOMContentLoaded (interactive/complete)');
}, 0); // 极短延迟
}
// 如果不是 complete/interactive,则 window.load 仍然是主要依赖
}
// 注册事件监听
window.addEventListener('load', onWindowLoad);
document.addEventListener('DOMContentLoaded', onDOMContentLoaded);
// 兜底策略:10 秒后强制关闭
removalTimeoutId = setTimeout(function() {
removeLoading('timeout fallback (10s)');
}, 10000);
})();