MediaWiki:Common.js:修订间差异
跳转到导航
跳转到搜索
导航栏新增虾仁音乐入口 |
SeniorDeveloper(留言 | 贡献) 优化自定义侧栏导航v2.1:健壮初始化、古风占位、错误降级 |
||
| 第1行: | 第1行: | ||
/* 虾仁维基 - 自定义侧边栏导航 */ | /* 虾仁维基 - 自定义侧边栏导航 v2.1 */ | ||
/* | /* 优化:古风主题统一、健壮初始化、精致样式 */ | ||
/* 最后更新: 2026-07- | /* 最后更新: 2026-07-30 */ | ||
(function($, mw) { | (function ($, mw) { | ||
'use strict'; | 'use strict'; | ||
| 第11行: | 第11行: | ||
function init() { | function init() { | ||
var wgIsArticle = mw.config.get('wgIsArticle'); | |||
var wgCanonicalSpecialPageName = mw.config.get('wgCanonicalSpecialPageName'); | |||
if (!wgIsArticle && wgCanonicalSpecialPageName) { | |||
return; | return; | ||
} | } | ||
waitForPanel(function($panel) { | mw.loader.using('mediawiki.api').then(function () { | ||
buildNav($panel); | waitForPanel(function ($panel) { | ||
buildNav($panel); | |||
}); | |||
}).catch(function (e) { | |||
console.error('[虾仁导航] 依赖加载失败,降级构建', e); | |||
waitForPanel(function ($panel) { | |||
buildNav($panel); | |||
}); | |||
}); | }); | ||
} | } | ||
| 第23行: | 第32行: | ||
if ($panel.length) return cb($panel); | if ($panel.length) return cb($panel); | ||
var tries = 0; | var tries = 0; | ||
var timer = setInterval(function() { | var timer = setInterval(function () { | ||
$panel = $('#mw-panel'); | $panel = $('#mw-panel'); | ||
if ($panel.length || ++tries > 50) { | if ($panel.length || ++tries > 50) { | ||
| 第31行: | 第40行: | ||
}, 100); | }, 100); | ||
} | } | ||
function buildNav($panel) { | function buildNav($panel) { | ||
var html = ''; | var html = ''; | ||
html += '<div class="xn-search">'; | html += '<div class="xn-search">'; | ||
html += '<form action="' + mw.util.getUrl('Special:Search') + '" method="get">'; | html += '<form action="' + mw.util.getUrl('Special:Search') + '" method="get">'; | ||
html += '<input type="text" name="search" class="xn-search-input" placeholder=" | html += '<input type="text" name="search" class="xn-search-input" placeholder="检索典籍…" />'; | ||
html += '</form></div>'; | html += '</form></div>'; | ||
| 第61行: | 第67行: | ||
html += '<div class="xn-section">'; | html += '<div class="xn-section">'; | ||
html += '<div class="xn-header xn-expandable" data-sid="universe">虾仁宇宙</div>'; | html += '<div class="xn-header xn-expandable" data-sid="universe">虾仁宇宙</div>'; | ||
html += '<div class="xn-body" id="xn-universe"><div class="xn-loading"> | html += '<div class="xn-body" id="xn-universe"><div class="xn-loading">卷帙加载中…</div></div>'; | ||
html += '</div>'; | html += '</div>'; | ||
| 第96行: | 第102行: | ||
loadUniverse(); | loadUniverse(); | ||
} | } | ||
function section(title, items) { | function section(title, items) { | ||
| 第120行: | 第124行: | ||
return '<a class="xn-plus" href="' + url(page) + '?action=edit" title="编辑/新增">+</a>'; | return '<a class="xn-plus" href="' + url(page) + '?action=edit" title="编辑/新增">+</a>'; | ||
} | } | ||
async function loadUniverse() { | async function loadUniverse() { | ||
| 第144行: | 第146行: | ||
}); | }); | ||
subpages = result.query.allpages || []; | subpages = result.query.allpages || []; | ||
} catch(e) { | } catch (e) { | ||
console.error('API error for ' + work, e); | console.error('[虾仁导航] API error for ' + work, e); | ||
} | } | ||
| 第152行: | 第154行: | ||
html += '<div class="xn-body" id="xn-' + workSid + '">'; | html += '<div class="xn-body" id="xn-' + workSid + '">'; | ||
var directSet = {}; | var directSet = {}; | ||
for (var j = 0; j < subpages.length; j++) { | for (var j = 0; j < subpages.length; j++) { | ||
| 第165行: | 第166行: | ||
} | } | ||
var plotSegs = []; | var plotSegs = []; | ||
for (j = 0; j < subpages.length; j++) { | for (j = 0; j < subpages.length; j++) { | ||
| 第185行: | 第185行: | ||
var segPage = work + '/剧情层/' + seg; | var segPage = work + '/剧情层/' + seg; | ||
var jinji = []; | var jinji = []; | ||
try { | try { | ||
| 第198行: | 第197行: | ||
jinji.push(segPages[m].title.substring(segPage.length + 1)); | jinji.push(segPages[m].title.substring(segPage.length + 1)); | ||
} | } | ||
} catch(e2) {} | } catch (e2) {} | ||
html += '<div class="xn-subsubsection">'; | html += '<div class="xn-subsubsection">'; | ||
| 第211行: | 第210行: | ||
} | } | ||
html += '</div></div>'; | html += '</div></div>'; | ||
html += '</div></div>'; | html += '</div></div>'; | ||
} | } | ||
html += '<div class="xn-link xn-add-work"><a href="' + url('Special:Search') + '" title="搜索已有作品">+ 发现更多作品</a></div>'; | html += '<div class="xn-link xn-add-work"><a href="' + url('Special:Search') + '" title="搜索已有作品">+ 发现更多作品</a></div>'; | ||
| 第223行: | 第221行: | ||
highlightCurrent(); | highlightCurrent(); | ||
} catch(e) { | } catch (e) { | ||
console.error(' | console.error('[虾仁导航] 虾仁宇宙加载失败', e); | ||
$body.html('<div class="xn-link">(加载失败,请刷新)</div>'); | $body.html('<div class="xn-link">(加载失败,请刷新)</div>'); | ||
} | } | ||
} | } | ||
function highlightCurrent() { | function highlightCurrent() { | ||
var currentTitle = mw.config.get('wgPageName'); | var currentTitle = mw.config.get('wgPageName'); | ||
$('#mw-panel .xn-link a').each(function() { | $('#mw-panel .xn-link a').each(function () { | ||
var href = $(this).attr('href'); | var href = $(this).attr('href'); | ||
if (!href) return; | if (!href) return; | ||
| 第248行: | 第244行: | ||
$(this).addClass('xn-active'); | $(this).addClass('xn-active'); | ||
$(this).closest('.xn-link').addClass('xn-active-link'); | $(this).closest('.xn-link').addClass('xn-active-link'); | ||
$(this).parents('.xn-body, .xn-subbody, .xn-subsubbody').each(function() { | $(this).parents('.xn-body, .xn-subbody, .xn-subsubbody').each(function () { | ||
$(this).show(); | $(this).show(); | ||
var sid = $(this).attr('id'); | var sid = $(this).attr('id'); | ||
| 第259行: | 第255行: | ||
}); | }); | ||
} | } | ||
function bindEvents() { | function bindEvents() { | ||
$(document).off('click.xn').on('click.xn', '.xn-expandable', function(e) { | $(document).off('click.xn').on('click.xn', '.xn-expandable', function (e) { | ||
if ($(e.target).closest('.xn-plus').length) return; | if ($(e.target).closest('.xn-plus').length) return; | ||
var sid = $(this).data('sid'); | var sid = $(this).data('sid'); | ||
var $body = $('#xn-' + sid); | var $body = $('#xn-' + sid); | ||
| 第285行: | 第280行: | ||
} | } | ||
Object.keys(state).forEach(function(sid) { | Object.keys(state).forEach(function (sid) { | ||
var $header = $('[data-sid="' + sid + '"]'); | var $header = $('[data-sid="' + sid + '"]'); | ||
var $body = $('#xn-' + sid); | var $body = $('#xn-' + sid); | ||
| 第303行: | 第298行: | ||
function loadState() { | function loadState() { | ||
try { return JSON.parse(localStorage.getItem(COLLAPSE_KEY) || '{}'); } | try { return JSON.parse(localStorage.getItem(COLLAPSE_KEY) || '{}'); } | ||
catch(e) { return {}; } | catch (e) { return {}; } | ||
} | } | ||
function saveState(s) { | function saveState(s) { | ||
try { localStorage.setItem(COLLAPSE_KEY, JSON.stringify(s)); } | try { localStorage.setItem(COLLAPSE_KEY, JSON.stringify(s)); } | ||
catch(e) {} | catch (e) {} | ||
} | } | ||
$(init); | |||
})(jQuery, mediaWiki); | })(jQuery, mediaWiki); | ||
2026年7月30日 (四) 00:22的版本
/* 虾仁维基 - 自定义侧边栏导航 v2.1 */
/* 优化:古风主题统一、健壮初始化、精致样式 */
/* 最后更新: 2026-07-30 */
(function ($, mw) {
'use strict';
var COLLAPSE_KEY = 'xiarenwiki-nav-v2';
var WORKS = ['我在大明那些年', '彼岸轮回', '一品布衣'];
var WORK_DIRECT = ['宇宙层', '人物', '世界观', '设定'];
function init() {
var wgIsArticle = mw.config.get('wgIsArticle');
var wgCanonicalSpecialPageName = mw.config.get('wgCanonicalSpecialPageName');
if (!wgIsArticle && wgCanonicalSpecialPageName) {
return;
}
mw.loader.using('mediawiki.api').then(function () {
waitForPanel(function ($panel) {
buildNav($panel);
});
}).catch(function (e) {
console.error('[虾仁导航] 依赖加载失败,降级构建', e);
waitForPanel(function ($panel) {
buildNav($panel);
});
});
}
function waitForPanel(cb) {
var $panel = $('#mw-panel');
if ($panel.length) return cb($panel);
var tries = 0;
var timer = setInterval(function () {
$panel = $('#mw-panel');
if ($panel.length || ++tries > 50) {
clearInterval(timer);
if ($panel.length) cb($panel);
}
}, 100);
}
function buildNav($panel) {
var html = '';
html += '<div class="xn-search">';
html += '<form action="' + mw.util.getUrl('Special:Search') + '" method="get">';
html += '<input type="text" name="search" class="xn-search-input" placeholder="检索典籍…" />';
html += '</form></div>';
html += section('Wiki', [
['首页', '首页'],
['Wiki简介', 'Wiki简介'],
['管理员告示', '管理员告示'],
['随机页面', 'Special:Random'],
['快速搜索', 'Special:Search'],
['相关平台', '相关平台'],
['Wiki源站点', 'Wiki源站点']
]);
html += section('虾仁简介', [
['虾仁?', '虾仁?'],
['虾仁共创收录规则', '虾仁共创收录规则'],
['虾仁宇宙结构介绍', '虾仁宇宙结构介绍']
]);
html += '<div class="xn-section">';
html += '<div class="xn-header xn-expandable" data-sid="universe">虾仁宇宙</div>';
html += '<div class="xn-body" id="xn-universe"><div class="xn-loading">卷帙加载中…</div></div>';
html += '</div>';
html += '<div class="xn-section">';
html += '<div class="xn-header xn-expandable" data-sid="literature">虾仁文学</div>';
html += '<div class="xn-body" id="xn-literature">';
html += link('创始宣言', '创始宣言');
html += link('虾仁六章', '虾仁六章');
html += link('虾仁异志', '虾仁异志');
html += link('虾仁诗作', '虾仁诗作');
html += link('虾仁音乐', '虾仁音乐');
html += '<div class="xn-subsection">';
html += '<div class="xn-subheader xn-expandable" data-sid="papers">虾仁论文 ' + plus('虾仁论文') + '</div>';
html += '<div class="xn-subbody" id="xn-papers">';
html += link('去中心化创作模式的研究', '去中心化创作模式的研究');
html += link('数字时代的孙悟空', '数字时代的孙悟空');
html += '</div></div></div></div>';
html += section('虾仁历史', [
['起源', '虾仁历史/起源'],
['传播', '虾仁历史/传播'],
['转折', '虾仁历史/转折'],
['爆发', '虾仁历史/爆发'],
['危机', '虾仁历史/危机'],
['转型', '虾仁历史/转型']
]);
html += '<div class="xn-single"><a href="' + url('共创入口') + '">共创入口</a></div>';
$panel.html(html);
restoreCollapse();
bindEvents();
highlightCurrent();
loadUniverse();
}
function section(title, items) {
var sid = title.replace(/[^a-z0-9\u4e00-\u9fff]/gi, '').toLowerCase();
var h = '<div class="xn-section">';
h += '<div class="xn-header xn-expandable" data-sid="' + sid + '">' + title + '</div>';
h += '<div class="xn-body" id="xn-' + sid + '">';
for (var i = 0; i < items.length; i++) {
h += link(items[i][0], items[i][1]);
}
h += '</div></div>';
return h;
}
function link(label, page, cls) {
return '<div class="xn-link' + (cls ? ' ' + cls : '') + '"><a href="' + url(page) + '">' + label + '</a></div>';
}
function url(page) { return mw.util.getUrl(page); }
function plus(page) {
return '<a class="xn-plus" href="' + url(page) + '?action=edit" title="编辑/新增">+</a>';
}
async function loadUniverse() {
var $body = $('#xn-universe');
if (!$body.length) return;
try {
var api = new mw.Api();
var html = '';
for (var i = 0; i < WORKS.length; i++) {
var work = WORKS[i];
var workSid = 'work' + i;
var subpages = [];
try {
var result = await api.get({
action: 'query',
list: 'allpages',
apprefix: work + '/',
aplimit: 100
});
subpages = result.query.allpages || [];
} catch (e) {
console.error('[虾仁导航] API error for ' + work, e);
}
html += '<div class="xn-work">';
html += '<div class="xn-header xn-expandable" data-sid="' + workSid + '">' + work + '</div>';
html += '<div class="xn-body" id="xn-' + workSid + '">';
var directSet = {};
for (var j = 0; j < subpages.length; j++) {
var rel = subpages[j].title.substring(work.length + 1);
if (rel.indexOf('/') === -1) directSet[rel] = true;
}
for (var k = 0; k < WORK_DIRECT.length; k++) {
var item = WORK_DIRECT[k];
var exists = directSet[item];
html += link(item, work + '/' + item, exists ? '' : 'xn-missing');
}
var plotSegs = [];
for (j = 0; j < subpages.length; j++) {
rel = subpages[j].title.substring(work.length + 1);
var parts = rel.split('/');
if (parts.length === 2 && parts[0] === '剧情层') {
plotSegs.push(parts[1]);
}
}
var plotSid = workSid + 'plot';
html += '<div class="xn-subsection">';
html += '<div class="xn-subheader xn-expandable" data-sid="' + plotSid + '">剧情层 ' + plus(work + '/剧情层') + '</div>';
html += '<div class="xn-subbody" id="xn-' + plotSid + '">';
for (k = 0; k < plotSegs.length; k++) {
var seg = plotSegs[k];
var segSid = workSid + 'seg' + k;
var segPage = work + '/剧情层/' + seg;
var jinji = [];
try {
var segResult = await api.get({
action: 'query',
list: 'allpages',
apprefix: segPage + '/',
aplimit: 50
});
var segPages = segResult.query.allpages || [];
for (var m = 0; m < segPages.length; m++) {
jinji.push(segPages[m].title.substring(segPage.length + 1));
}
} catch (e2) {}
html += '<div class="xn-subsubsection">';
html += '<div class="xn-subsubheader xn-expandable" data-sid="' + segSid + '">' + seg + ' ' + plus(segPage) + '</div>';
html += '<div class="xn-subsubbody" id="xn-' + segSid + '">';
for (m = 0; m < jinji.length; m++) {
html += link(jinji[m], segPage + '/' + jinji[m]);
}
html += '</div></div>';
}
html += '</div></div>';
html += '</div></div>';
}
html += '<div class="xn-link xn-add-work"><a href="' + url('Special:Search') + '" title="搜索已有作品">+ 发现更多作品</a></div>';
$body.html(html);
restoreCollapse();
bindEvents();
highlightCurrent();
} catch (e) {
console.error('[虾仁导航] 虾仁宇宙加载失败', e);
$body.html('<div class="xn-link">(加载失败,请刷新)</div>');
}
}
function highlightCurrent() {
var currentTitle = mw.config.get('wgPageName');
$('#mw-panel .xn-link a').each(function () {
var href = $(this).attr('href');
if (!href) return;
var match = false;
if (href.indexOf('title=') > 0) {
var m = href.match(/title=([^&]+)/);
if (m) match = decodeURIComponent(m[1]) === currentTitle;
} else {
var parts = href.split('/');
var last = parts[parts.length - 1];
match = decodeURIComponent(last) === currentTitle;
}
if (match) {
$(this).addClass('xn-active');
$(this).closest('.xn-link').addClass('xn-active-link');
$(this).parents('.xn-body, .xn-subbody, .xn-subsubbody').each(function () {
$(this).show();
var sid = $(this).attr('id');
if (sid && sid.indexOf('xn-') === 0) {
sid = sid.substring(3);
$('[data-sid="' + sid + '"]').removeClass('xn-collapsed');
}
});
}
});
}
function bindEvents() {
$(document).off('click.xn').on('click.xn', '.xn-expandable', function (e) {
if ($(e.target).closest('.xn-plus').length) return;
var sid = $(this).data('sid');
var $body = $('#xn-' + sid);
if (!$body.length) $body = $(this).next('.xn-body,.xn-subbody,.xn-subsubbody');
if (!$body.length) return;
$body.slideToggle(150);
$(this).toggleClass('xn-collapsed', !$body.is(':visible'));
var state = loadState();
state[sid] = $body.is(':visible');
saveState(state);
});
}
function restoreCollapse() {
var state = loadState();
var defaults = { 'universe': true };
for (var key in defaults) {
if (!(key in state)) state[key] = defaults[key];
}
Object.keys(state).forEach(function (sid) {
var $header = $('[data-sid="' + sid + '"]');
var $body = $('#xn-' + sid);
if (!$body.length) $body = $header.next('.xn-body,.xn-subbody,.xn-subsubbody');
if (!$body.length) return;
if (state[sid]) {
$body.show();
$header.removeClass('xn-collapsed');
} else {
$body.hide();
$header.addClass('xn-collapsed');
}
});
}
function loadState() {
try { return JSON.parse(localStorage.getItem(COLLAPSE_KEY) || '{}'); }
catch (e) { return {}; }
}
function saveState(s) {
try { localStorage.setItem(COLLAPSE_KEY, JSON.stringify(s)); }
catch (e) {}
}
$(init);
})(jQuery, mediaWiki);