MediaWiki:Common.js:修订间差异

虾仁留言 | 贡献
导航栏新增虾仁音乐入口
SeniorDeveloper留言 | 贡献
优化自定义侧栏导航v2.1:健壮初始化、古风占位、错误降级
第1行: 第1行:
/* 虾仁维基 - 自定义侧边栏导航 */
/* 虾仁维基 - 自定义侧边栏导航 v2.1 */
/* 作者: 虾仁维基团队 */
/* 优化:古风主题统一、健壮初始化、精致样式 */
/* 最后更新: 2026-07-26 */
/* 最后更新: 2026-07-30 */


(function($, mw) {
(function ($, mw) {
     'use strict';
     'use strict';


第11行: 第11行:


     function init() {
     function init() {
         if (!mw.config.get('wgIsArticle') && mw.config.get('wgCanonicalSpecialPageName')) {
         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);
     }
     }
    /* ========== BUILD ========== */


     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">加载中...</div></div>';
         html += '<div class="xn-body" id="xn-universe"><div class="xn-loading">卷帙加载中…</div></div>';
         html += '</div>';
         html += '</div>';


第96行: 第102行:
         loadUniverse();
         loadUniverse();
     }
     }
    /* ========== HTML HELPERS ========== */


     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>';
     }
     }
    /* ========== UNIVERSE DYNAMIC LOAD ========== */


     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 + '">';


                // Direct subpages under work
                 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;


                    // Fetch 金集 for this segment
                     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>'; // close 剧情层
                 html += '</div></div>';
                 html += '</div></div>'; // close work
                 html += '</div></div>';
             }
             }


            // Add new work link
             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('Failed to load universe nav', e);
             console.error('[虾仁导航] 虾仁宇宙加载失败', e);
             $body.html('<div class="xn-link">(加载失败,请刷新)</div>');
             $body.html('<div class="xn-link">(加载失败,请刷新)</div>');
         }
         }
     }
     }


    /* ========== CURRENT PAGE HIGHLIGHT ========== */
     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行:
         });
         });
     }
     }
    /* ========== EVENTS & STATE ========== */


     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; // don't toggle when clicking [+]
             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) {}
     }
     }


     /* ========== START ========== */
     $(init);
 
    mw.loader.using('mediawiki.api').then(function() {
        $(init);
    });


})(jQuery, mediaWiki);
})(jQuery, mediaWiki);