如有延迟,点此刷新首页
本WIKI编辑权限开放,欢迎收藏起来防止迷路,也希望有爱的小伙伴和我们一起编辑哟~
Chrome浏览器登陆后无法编辑点这里
编辑帮助:目录 • BWIKI反馈留言板
如有延迟,点此刷新首页
本WIKI编辑权限开放,欢迎收藏起来防止迷路,也希望有爱的小伙伴和我们一起编辑哟~
编辑帮助:目录 • BWIKI反馈留言板
Widget:BLoader
BLoader - BWiki资源加载器
对于加载了BLoader的页面,可以用模板:Load加载指定的JS、CSS文件。
源码:
<script>
// 这里用来定义 tools wiki需要加载的JS、CSS
// 预定义资源列表, path: 'wiki/file.css' , timing: 'immediate'(default) | 'dom' | 'jquery' | 'complete'
(window.BLoaderList = window.BLoaderList || []).push(
{
path: 'tools/MediaWiki:Main.js',
timing: 'immediate'
},
{
path: 'tools/MediaWiki:Main.css',
timing: 'immediate'
},
); // 务必在之后加载BLoader。如果BLoder已经加载过,就不会加载这些资源了
</script>
<script>
/**
* BLoader.js - BWiki资源加载器
*
* 用于加载CSS和JS资源
* 控制加载时机:DOMContentLoaded、jQuery加载、window.onload、立即加载
* 支持作为Widget放入SiteNotice中
* 支持放入Tampermonkey中
*
* 不会重复加载
* 支持对管理员/界面管理员绕过缓存,默认对界面管理员绕过缓存
* 支持动态加载:页面中被动态添加 .loader.js 和 .loader.css 元素时,会触发自动加载
*
*
* 代码几乎全部由 Roo Cline + DeepSeek-V3 完成, GitHub Copilot (GPT 4o) 负责微调细节, Lu提供引导、注释和测试。
*
*
* 使用示例:
* 1. 加载单个CSS:
* 在页面中添加如下元素:
* <div class="loader css" style="display:none">style.css</div>
*
* 可以自定义模板来快速构造此HTML
*
* 2. 加载单个JS:
* 在页面中添加如下元素:
* <div class="loader js" data-load-timing="dom" data-debug="true" style="display:none">script.js</div>
*
* 可以自定义模板来快速构造此HTML
*
* 其中 data-load-timing 可选值为: dom | jquery | complete | immediate (default)
* * dom: DOMContentLoaded 时加载
* * jquery: jQuery加载后加载, 最长等待5秒后立即加载
* * complete: window.onload 时加载
* * immediate: 立即加载(默认值)
* 如果执行加载时,已经错过了对应的时机,则立即加载
*
* 3. 预定义资源列表:
* 修改 BLoaderList 数组,每个元素包含 path、timing、debug,分别表示资源路径、加载时机和绕过缓存,加载时机如上所述
* path 为资源路径,如 'wiki/file.css' 或 'tools/file.js', 需要带有wiki域名作为前缀,这是为了便于加载其他wiki的资源
* 如:
// 预定义资源列表, path: 'wiki/file.css' , timing: 'immediate'(default) | 'dom' | 'jquery' | 'complete'
(window.BLoaderList = window.BLoaderList || []).push(
{
path: 'tools/MediaWiki:Prism.css',
timing: 'jquery',
debug: true
},
{
path: 'blhx/MediaWiki:xxxx.css',
debug: true
},
{
path: 'wiki/MediaWiki:Demo.js',
timing: 'dom'
},
// 在此添加更多资源
); // 务必在之后加载BLoader。如果BLoder已经加载过,不会加载这些资源
*
*
* 代码分为以下部分:
* 0. 辅助函数:输出页面加载生命周期,用于调试
*
* 1. BLoader - 资源加载器,加载CSS和JS资源
* 监听页面中的 .loader.js 和 .loader.css 元素,以其标签内文本作为资源名称加载,仅支持本wiki MW空间,如 Mediawiki:resource.css
*
* 2. AdminGroupCache - 缓存高权限用户的用户组(管理员、界面管理员)到 localStorage,其他程序如 BLoader 可以针对用户组免缓存加载资源
* 定时刷新用户组信息到localStorage,供BLoader等程序使用
*
*
*/
(function() { // 辅助函数:添加日志,用于调试
function addLog(message) {
var timestamp = new Date().toLocaleTimeString();
console.debug('%c[' + timestamp + '] ' + message, 'color: green');
}
// 脚本开始执行
addLog('BLoader脚本开始执行');
// DOMContentLoaded 事件:当初始的 HTML 文档被完全加载和解析完成时触发,不需要等待样式表、图像和子框架的完成加载
document.addEventListener('DOMContentLoaded', function() {
addLog('DOMContentLoaded 事件触发:DOM 完全加载和解析');
});
// window.onload 事件:当页面的所有资源(如图片、样式表)都已加载完成时触发
window.addEventListener('load', function() {
addLog('load 事件触发:页面及所有资源完全加载');
});
function waitjQuery(waitCount) { // 等待jQuery加载,最多等待10秒
if (window.jQuery || waitCount > 200) {
addLog('jquery 事件触发:jquery加载了!');
} else{
setTimeout(function() {waitjQuery(waitCount + 1)}, 50);
}
}
waitjQuery(0);
})();
// BLoader
(function() {
var noCacheForSysop = false; // 对 管理员 绕过缓存
var noCacheForInterfaceAdmin = true; // 对 界面管理员 绕过缓存
var cacheKey = 'BLoaderAdminGroupsCache'; //如需修改,请同步修改AdminGroupCache
var JQ_INTERVAL = 81; // jQuery检查间隔(81毫秒)
var MAX_WAIT_COUNT = 64; // 最大等待次数(81*64=5184毫秒)
var bwiki_url = 'https://wiki.biligame.com/'; // bwiki域名
var wikiName = typeof RLCONF === 'object' ? RLCONF.wgGameName : new URL(window.location.href).pathname.split('/')[1]; // 当前wiki名称
var loadedResources = new Set(); // 已加载资源URL集合,避免重复加载
var needNoCache = isNeedNoCache(); // 对当前用户计算是否需要禁用缓存
function loadCSS(path, debug) { // 加载CSS资源, path, 如 'wiki/file.css'
if (loadedResources.has(path)) {
console.debug('此CSS已经加载过,不再重复加载: ' + path);
return;
}
var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = formatUrl(bwiki_url + path, debug);// 仅限加载bwiki资源。如果需要禁用缓存,则在URL后添加随机参数
document.head.appendChild(link);
console.debug('BLoader 加载 CSS: ' + link.href);
loadedResources.add(path);
};
function loadJS(path, timing, debug) { // 加载JS资源, path, 如 'wiki/file.js'
if (loadedResources.has(path)) {
console.debug('此JS已经加载过,不再重复加载: ' + path);
return;
}
var script = document.createElement('script'); // 创建script标签
script.src = formatUrl(bwiki_url + path, debug); // 仅限加载bwiki资源。如果需要禁用缓存,则在URL后添加随机参数
loadedResources.add(path); // 存入已加载集合,标记此资源已加载
function appendScript() { // 添加script标签到head, 写成函数便于下方使用
document.head.appendChild(script);
console.debug('BLoader 加载 JS: ' + script.src);
};
function waitjQuery(waitCount) { // 等待jQuery加载,最多等待MAX_WAIT_COUNT次
if (window.jQuery || waitCount > MAX_WAIT_COUNT) {
appendScript();
} else{
setTimeout(function() {waitjQuery(waitCount + 1)}, JQ_INTERVAL);
}
}
switch (timing) {
case 'dom':
document.readyState !== 'loading' ? appendScript() : document.addEventListener('DOMContentLoaded', appendScript);
break;
case 'jquery':
waitjQuery(0);
break;
case 'complete':
document.readyState === 'complete' ? appendScript() : window.addEventListener('load', appendScript);
break;
default:
appendScript();
break;
}
}
function isNeedNoCache() { // 是否需要禁用缓存,有相关用户组,且上方用户组配置为true
try {
var adminGroups = JSON.parse(localStorage.getItem(cacheKey)) || {};
var userGroups = adminGroups[wikiName] || {};
return (userGroups['sysop'] && noCacheForSysop) || (userGroups['interface-admin'] && noCacheForInterfaceAdmin);
} catch (error) {
console.error('Failed to parse admin groups from localStorage:', error);
return false;
}
}
function formatUrl(url, debug) { // 如果需要禁用缓存,则在URL后添加随机参数
if (url.toLowerCase().endsWith('.css')) {
url = url + '?action=raw&ctype=text/css'; // 以raw形式加载css
} else {
url = url + '?action=raw&ctype=text/javascript'; // 以raw形式加载js
}
if (needNoCache || debug){
url = url + '&nocache=1&random=' + Math.ceil(Date.now() / 1000);
}
return url;
}
// 处理页面中的 .loader.js 和 .loader.css 元素,以其标签内文本作为资源名称加载,仅支持本wiki MW空间,如 Mediawiki:resource.css。
function loadFromElements() {
function getFullPath(name, type) {
name = 'MediaWiki:' + (name.toLowerCase().startsWith('mediawiki:') ? name.slice(10) : name); // 去除Mediawiki:前缀,下文统一加,避免大小写问题导致重定向
name = name.toLowerCase().endsWith('.' + type) ? name : name + '.' + type; // 确保包含正确的后缀,即必须以type(js/css)结尾
return wikiName + '/' + name;
}
function processElement(el, type) {
try {
var name = el.textContent.trim();
if (type === 'css') {
loadCSS(getFullPath(name, type), el.dataset.debug === 'true'); // 仅支持wiki自身内容,避免用户有权限跨站注入js
} else {
loadJS(getFullPath(name, type), el.dataset.loadTiming || 'immediate', el.dataset.debug === 'true');
}
el.dataset.processed = true; // 标记此标签已处理,即使重复调用也不会重复加载
} catch (error) {
console.error('Failed to process loader:', type, el, error);
}
};
document.querySelectorAll('#content .loader.css:not([data-processed])').forEach(function (el) {
processElement(el, 'css');
});
document.querySelectorAll('#content .loader.js:not([data-processed])').forEach(function (el) {
processElement(el, 'js');
});
}
// 节流函数,限制触发频率
function throttle(func) {
var inThrottle = false;
return function() {
if (!inThrottle) {
func();
inThrottle = true;
setTimeout(function(){inThrottle = false;}, 100);// 100ms
}
};
}
function watchdog() {
// 避免重复加载 watchdog,因为MutationObserver会在每次变化时调用,多次加载会浪费资源
if (window.BLoader_IsLoaded) {
return;
}
window.BLoader_IsLoaded = true;
loadFromElements();
var content = document.querySelector('#content');
if (!content) {
content = document.body;
}
var observer = new MutationObserver(throttle(loadFromElements));
observer.observe(content, { childList: true, subtree: true });
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', watchdog);
} else {
watchdog();
}
(window.BLoaderList = window.BLoaderList || []).forEach(function (res) { // 加载预定义资源列表中的资源
if (res.path.endsWith('.css')) {
loadCSS(res.path, res.debug || false);
} else {
loadJS(res.path, res.timing || 'immediate', res.debug || false);
}
});
loadFromElements(); // 此时dom可能没有加载完全,只能加载部分资源,后续会通过MutationObserver继续加载
})();
// AdminGroupCache - 缓存高权限用户的用户组(管理员、界面管理员)到 localStorage,其他程序如 BLoader 可以针对用户组免缓存加载资源
(function() {
function purgeAdminGroupCache() {
var apiUrl = 'https://wiki.biligame.com/' + RLCONF.wgGameName + '/api.php?action=query&meta=userinfo&uiprop=groups&format=json';
// localStorage 缓存配置
var cacheKey = 'BLoaderAdminGroupsCache'; // 如需修改,请同步修改BLoader
var cacheExpiryKey = cacheKey + '_expiry';
var cacheExpiryTime = 5 * 60 * 1000; // 5 minutes
function purgeCache() { /* 刷新localStorage的缓存 */
fetch(apiUrl)
.then(function(response){return response.json();})
.then(function(data) {
var groups = (data && data.query && data.query.userinfo && data.query.userinfo.groups) || [];
var adminGroups = JSON.parse(localStorage.getItem(cacheKey)) || {};
adminGroups[RLCONF.wgGameName] = {
'sysop': groups.includes('sysop'),
'interface-admin': groups.includes('interface-admin')
};
localStorage.setItem(cacheKey, JSON.stringify(adminGroups));
localStorage.setItem(cacheExpiryKey, Date.now() + cacheExpiryTime);
})
.catch(function(error){
console.error('Failed to fetch and update admin groups:', error);
});
}
function isCacheExpired() {
try{
var expiryTime = localStorage.getItem(cacheExpiryKey);
return !expiryTime || Date.now() > expiryTime;
} catch (error) {
console.error('Failed to check localStorage cache expiry:', error);
return true;
}
}
if (isCacheExpired()) {
purgeCache();
}
}
if (document.readyState === 'loading') { // 等dom加载完再执行,避免在油猴等环境下执行过早,无法获取RLCONF
document.addEventListener('DOMContentLoaded', purgeAdminGroupCache);
} else {
purgeAdminGroupCache();
}
})();
</script>