MediaWiki:Common.js:修订间差异

来自Mindustry中文wiki
无编辑摘要
无编辑摘要
标签已被回退
第441行: 第441行:
     }
     }
   });
   });
})();
(function () {
  'use strict';
  function onReady(fn) {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', fn);
    } else {
      fn();
    }
  }
  function el(tag, cls) {
    var n = document.createElement(tag);
    if (cls) n.className = cls;
    return n;
  }
  function svgIcon(pathD) {
    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    var p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    p.setAttribute('d', pathD);
    svg.appendChild(p);
    return svg;
  }
  function findPortletUl(idCandidates) {
    for (var i = 0; i < idCandidates.length; i++) {
      var p = document.getElementById(idCandidates[i]);
      if (!p) continue;
      var ul = p.querySelector('.mw-portlet-body > ul') || p.querySelector('ul');
      if (ul) return ul;
    }
    return null;
  }
  function clonePortlet(title, ul) {
    var wrap = el('div', 'mdtDockSection');
    var h = el('div', 'mdtDockSectionTitle');
    h.textContent = title;
    wrap.appendChild(h);
    var cloned = ul.cloneNode(true);
    // 清掉某些 id,避免重复
    cloned.removeAttribute('id');
    wrap.appendChild(cloned);
    return wrap;
  }
  function initDock() {
    // 可选:只在手机 / 小屏启用。你想全端都启用就删掉这段判断。
    // if (window.matchMedia && window.matchMedia('(min-width: 980px)').matches) return;
    var body = document.body;
    if (!body || body.classList.contains('mdtDockEnabled')) return;
    body.classList.add('mdtDockEnabled');
    var overlay = el('div', 'mdtDockOverlay');
    overlay.addEventListener('click', function () {
      body.classList.remove('mdtDockDrawerOpen');
    });
    var drawer = el('aside', 'mdtDockDrawer');
    var drawerInner = el('div', 'mdtDockDrawerInner');
    drawer.appendChild(drawerInner);
    // 把原本的侧边栏内容“克隆”进抽屉
    // Timeless: p-navigation, p-tb
    // Vector/others: p-navigation, p-tb 也常见
    var nav = findPortletUl(['p-navigation', 'p-Navigation']);
    if (nav) drawerInner.appendChild(clonePortlet('导航', nav));
    var tools = findPortletUl(['p-tb', 'p-Toolbox']);
    if (tools) drawerInner.appendChild(clonePortlet('wiki工具', tools));
    // 折叠树:把 "-- " 前缀的条目变成可折叠子项
    function buildNavTree(ulRoot) {
      if (!ulRoot) return;
      if (ulRoot.getAttribute('data-mdtNav') === '1') return;
      ulRoot.setAttribute('data-mdtNav', '1');
      var KEY = 'mdtNavTreeOpen';
      var store = {};
      try {
        store = JSON.parse(localStorage.getItem(KEY) || '{}');
      } catch (e) {
        store = {};
      }
      function getKey(li) {
        if (li && li.id) return li.id;
        var a = li ? li.querySelector('a') : null;
        return a ? (a.getAttribute('href') || '') : '';
      }
      function setStored(li, open) {
        var k = getKey(li);
        if (!k) return;
        store[k] = open;
        try {
          localStorage.setItem(KEY, JSON.stringify(store));
        } catch (e) {}
      }
      function setOpen(li, open, persist) {
        li.classList.toggle('mdtNavOpen', open);
        if (persist) setStored(li, open);
      }
      function ensureGroup(li) {
        li.classList.add('mdtNavGroup');
        var sub = li.querySelector('ul.mdtNavSublist');
        if (!sub) {
          sub = document.createElement('ul');
          sub.className = 'mdtNavSublist';
          li.appendChild(sub);
        }
        var btn = li.querySelector('button.mdtNavToggle');
        if (!btn) {
          btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'mdtNavToggle';
          btn.setAttribute('aria-label', '展开/收起');
          btn.setAttribute('aria-expanded', 'false');
          li.insertBefore(btn, sub);
          btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            ev.stopPropagation();
            var isOpen = li.classList.contains('mdtNavOpen');
            setOpen(li, !isOpen, true);
            btn.setAttribute('aria-expanded', String(!isOpen));
          });
        }
        var k = getKey(li);
        if (store[k] === true) {
          li.classList.add('mdtNavOpen');
          btn.setAttribute('aria-expanded', 'true');
        }
        return sub;
      }
      var items = Array.prototype.slice.call(ulRoot.children);
      var lastAtDepth = [];
      for (var i = 0; i < items.length; i++) {
        var li = items[i];
        if (!li || li.tagName !== 'LI') continue;
        var a = li.querySelector('a');
        if (!a) continue;
        var raw = (a.textContent || '').trim();
        var m = raw.match(/^(-{2,})\s*(.*)$/);
        var depth = 0;
        if (m) {
          depth = Math.min(Math.floor(m[1].length / 2), 3);
          a.textContent = m[2];
        }
        if (depth > 0 && lastAtDepth[depth - 1]) {
          li.classList.add('mdtNavSubItem');
          li.classList.add('mdtNavDepth' + String(depth));
          var sublist = ensureGroup(lastAtDepth[depth - 1]);
          sublist.appendChild(li);
        } else {
          depth = 0;
        }
        lastAtDepth[depth] = li;
        lastAtDepth.length = depth + 1;
      }
      // 当前页面高亮
      try {
        if (typeof mw !== 'undefined' && mw.config) {
          var current = (mw.config.get('wgPageName') || '').split(' ').join('_');
          var links = ulRoot.querySelectorAll('a');
          for (var j = 0; j < links.length; j++) {
            var href = links[j].getAttribute('href') || '';
            if (!href) continue;
            if (href.indexOf('title=') !== -1 && typeof mw.util !== 'undefined') {
              var t = mw.util.getParamValue('title', href);
              if (t && t.split(' ').join('_') === current) {
                links[j].classList.add('mdtDockActiveLink');
                break;
              }
            }
          }
        }
      } catch (e) {}
    }
    // 对抽屉中的导航/工具分别构建折叠树
    var drawerSections = drawerInner.querySelectorAll('.mdtDockSection > ul');
    for (var s = 0; s < drawerSections.length; s++) {
      buildNavTree(drawerSections[s]);
    }
    // 你也可以把其它 portlet 加进来,例如:
    // var lang = findPortletUl(['p-lang']);
    // if (lang) drawerInner.appendChild(clonePortlet('语言', lang));
    // 左侧图标栏
    var dock = el('nav', 'mdtDock');
    // 图标:简单用 SVG path(不依赖字体/图片)
    // home
    var btnHome = el('a', 'mdtDockBtn');
    btnHome.href = '/';
    btnHome.title = '首页';
    btnHome.appendChild(svgIcon('M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'));
    dock.appendChild(btnHome);
    // search(点击聚焦搜索框)
    var btnSearch = el('button', 'mdtDockBtn');
    btnSearch.type = 'button';
    btnSearch.title = '搜索';
    btnSearch.appendChild(svgIcon('M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zM9.5 14C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'));
    btnSearch.addEventListener('click', function () {
      // Timeless 常见 #searchInput,Vector 常见 input[name=search]
      var input = document.getElementById('searchInput') ||
        document.querySelector('input[name=search]') ||
        document.querySelector('#searchform input[type=search]');
      if (input) input.focus();
    });
    dock.appendChild(btnSearch);
    // menu(打开抽屉)
    var btnMenu = el('button', 'mdtDockBtn mdtDockBtnActive');
    btnMenu.type = 'button';
    btnMenu.title = '导航菜单';
    btnMenu.appendChild(svgIcon('M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z'));
    btnMenu.addEventListener('click', function () {
      body.classList.toggle('mdtDockDrawerOpen');
    });
    dock.appendChild(btnMenu);
    dock.appendChild(el('div', 'mdtDockSpacer'));
    // settings(先做占位:你以后可做主题切换等)
    var btnSettings = el('button', 'mdtDockBtn');
    btnSettings.type = 'button';
    btnSettings.title = '设置';
    btnSettings.appendChild(svgIcon('M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.11-.2-.36-.28-.57-.2l-2.39.96c-.5-.38-1.04-.69-1.64-.92l-.36-2.54A.488.488 0 0014.41 2h-3.82c-.24 0-.44.17-.48.41l-.36 2.54c-.6.23-1.14.54-1.64.92l-2.39-.96c-.21-.08-.46 0-.57.2L2.23 8.43c-.11.2-.06.46.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.5.5 0 00-.12.61l1.92 3.32c.11.2.36.28.57.2l2.39-.96c.5.38 1.04.69 1.64.92l.36 2.54c.04.24.24.41.48.41h3.82c.24 0 .44-.17.48-.41l.36-2.54c.6-.23 1.14-.54 1.64-.92l2.39.96c.21.08.46 0 .57-.2l1.92-3.32a.5.5 0 00-.12-.61l-2.03-1.58zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5S10.07 8.5 12 8.5s3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z'));
    btnSettings.addEventListener('click', function () {
      alert('这里可以做:主题切换/字号等');
    });
    dock.appendChild(btnSettings);
    // 组装
    document.body.appendChild(overlay);
    document.body.appendChild(drawer);
    document.body.appendChild(dock);
    // Esc 关闭抽屉
    document.addEventListener('keydown', function (ev) {
      if (ev.key === 'Escape') {
        body.classList.remove('mdtDockDrawerOpen');
      }
    });
    // 注意:如果你已经在 Common.js 里有同名的折叠树脚本,也没关系,
    // 这里对 clone 的 ulRoot 设置了 data-mdtNav=1,避免重复处理。
  }
  onReady(initDock);
})();
})();

2026年1月21日 (三) 20:32的版本

/* MediaWiki:Common.js */
(function () {
  'use strict';

  function fallbackCopy(text) {
    if (!window.jQuery) return;
    var $ = window.jQuery;
    var $temp = $('<textarea>');
    $('body').append($temp);
    $temp.val(text).select();
    document.execCommand('copy');
    $temp.remove();
    alert('复制成功!');
  }

  // 点击按钮复制内容:<button class="copy-button" data-text="...">复制</button>
  if (window.jQuery) {
    window.jQuery(function ($) {
      $(document).on('click', '.copy-button', function () {
        var text = $(this).attr('data-text') || '';
        if (navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(text).then(function () {
            alert('复制成功!');
          }, function () {
            fallbackCopy(text);
          });
        } else {
          fallbackCopy(text);
        }
      });
    });
  }

  // Timeless:把 Sidebar 里以 "-- " 开头的条目变成可折叠子项
  mw.loader.using(['mediawiki.util'], function () {
    if (mw.config.get('skin') !== 'timeless') return;

    function initNavTree() {
      var siteNav = document.getElementById('mw-site-navigation');
      if (!siteNav) return;

      var pNav = siteNav.querySelector('#p-navigation');
      if (!pNav) return;

      var root = pNav.querySelector('.mw-portlet-body > ul') ||
        pNav.querySelector('ul');
      if (!root) return;

      if (root.getAttribute('data-mdtNav') === '1') return;
      root.setAttribute('data-mdtNav', '1');

      var KEY = 'mdtNavTreeOpen';
      var store = {};
      try {
        store = JSON.parse(localStorage.getItem(KEY) || '{}');
      } catch (e) {
        store = {};
      }

      function getKey(li) {
        if (li && li.id) return li.id;
        var a = li ? li.querySelector('a') : null;
        return a ? (a.getAttribute('href') || '') : '';
      }

      function setStored(li, open) {
        var k = getKey(li);
        if (!k) return;
        store[k] = open;
        try {
          localStorage.setItem(KEY, JSON.stringify(store));
        } catch (e) {}
      }

      function setOpen(li, open, persist) {
        li.classList.toggle('mdtNavOpen', open);
        if (persist) setStored(li, open);
      }

      function ensureGroup(li) {
        li.classList.add('mdtNavGroup');

        var sub = li.querySelector('ul.mdtNavSublist');
        if (!sub) {
          sub = document.createElement('ul');
          sub.className = 'mdtNavSublist';
          li.appendChild(sub);
        }

        var btn = li.querySelector('button.mdtNavToggle');
        if (!btn) {
          btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'mdtNavToggle';
          btn.setAttribute('aria-label', '展开/收起');
          btn.setAttribute('aria-expanded', 'false');
          li.insertBefore(btn, sub);

          btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            ev.stopPropagation();
            var isOpen = li.classList.contains('mdtNavOpen');
            setOpen(li, !isOpen, true);
            btn.setAttribute('aria-expanded', String(!isOpen));
          });
        }

        var k = getKey(li);
        if (store[k] === true) {
          li.classList.add('mdtNavOpen');
          btn.setAttribute('aria-expanded', 'true');
        }

        return sub;
      }

      var items = Array.prototype.slice.call(root.children);
      var lastAtDepth = [];

      for (var i = 0; i < items.length; i++) {
        var li = items[i];
        if (!li || li.tagName !== 'LI') continue;

        var a = li.querySelector('a');
        if (!a) continue;

        var raw = (a.textContent || '').trim();
        var m = raw.match(/^(-{2,})\s*(.*)$/);

        var depth = 0;
        if (m) {
          depth = Math.min(Math.floor(m[1].length / 2), 3);
          a.textContent = m[2];
        }

        li.classList.add('mdtNavItem');

        if (depth > 0 && lastAtDepth[depth - 1]) {
          li.classList.add('mdtNavSubItem');
          li.classList.add('mdtNavDepth' + String(depth));
          var sublist = ensureGroup(lastAtDepth[depth - 1]);
          sublist.appendChild(li);
        } else {
          depth = 0;
        }

        lastAtDepth[depth] = li;
        lastAtDepth.length = depth + 1;
      }

      // 当前页面高亮 + 自动展开父级
      var current = (mw.config.get('wgPageName') || '').replace(/ /g, '_');
      var links = pNav.querySelectorAll('a');

      function titleFromHref(href) {
        var t = mw.util.getParamValue('title', href);
        if (t) return t.replace(/ /g, '_');

        var mark = '/index.php/';
        var pos = href.indexOf(mark);
        if (pos !== -1) {
          return decodeURIComponent(href.slice(pos + mark.length))
            .replace(/ /g, '_');
        }

        return '';
      }

      for (var j = 0; j < links.length; j++) {
        var href = links[j].getAttribute('href') || '';
        var page = titleFromHref(href);

        if (page && page === current) {
          links[j].classList.add('mdtNavActive');

          var n = links[j].parentNode;
          while (n && n !== root) {
            if (n.tagName === 'LI' && n.classList.contains('mdtNavGroup')) {
              setOpen(n, true, false);
              var btn = n.querySelector('button.mdtNavToggle');
              if (btn) btn.setAttribute('aria-expanded', 'true');
            }
            n = n.parentNode;
          }
          break;
        }
      }
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initNavTree);
    } else {
      initNavTree();
    }
  });

  // 可折叠标题:在标题旁边加一个展开/收起按钮
  // 用法:把任意标题写成:== 战役区块 <span class="mdtFold"></span> ==
  // 默认收起:== 战役区块 <span class="mdtFold mdtFoldClosed"></span> ==
  mw.loader.using(['mediawiki.util'], function () {
    var skin = String(mw.config.get('skin') || '').toLowerCase();
    if (skin !== 'timeless') return;

    var action = String(mw.config.get('wgAction') || 'view').toLowerCase();
    if (action === 'edit' || action === 'submit') return;

    function findHeading(node) {
      while (node && node !== document.body) {
        if (node.nodeType === 1 && /^H[1-6]$/.test(node.tagName)) return node;
        node = node.parentNode;
      }
      return null;
    }

    var KEY = 'mdtFoldSections';
    var store = {};
    try {
      store = JSON.parse(localStorage.getItem(KEY) || '{}');
    } catch (e) {
      store = {};
    }

    function saveStore() {
      try {
        localStorage.setItem(KEY, JSON.stringify(store));
      } catch (e) {}
    }

    function getHeadingBlock(headingEl) {
      if (!headingEl || !headingEl.parentElement) return headingEl;

      // MediaWiki 新版可能会把 h2 包在 <div class="mw-heading"> 里。
      // 如果不处理这个 wrapper,折叠只会影响 wrapper 内部(看起来“没效果”)。
      var p = headingEl.parentElement;
      if (p.classList && p.classList.contains('mw-heading')) return p;
      return headingEl;
    }

    function getHeadingLevelFromNode(node) {
      if (!node || node.nodeType !== 1) return null;

      if (/^H[1-6]$/.test(node.tagName)) {
        return parseInt(String(node.tagName).slice(1), 10) || 6;
      }

      if (node.classList && node.classList.contains('mw-heading')) {
        var h = node.querySelector('h1,h2,h3,h4,h5,h6');
        if (h && /^H[1-6]$/.test(h.tagName)) {
          return parseInt(String(h.tagName).slice(1), 10) || 6;
        }
      }

      return null;
    }

    function initFoldSections(root) {
      var content = root || document.getElementById('mw-content-text') ||
        document.querySelector('.mw-parser-output') ||
        document.body;
      if (!content) return;

      var nodes = [];
      var markers = content.querySelectorAll('.mdtFold');
      var headingNodes = content.querySelectorAll('h1.mdtFoldHeading,h2.mdtFoldHeading,h3.mdtFoldHeading,h4.mdtFoldHeading,h5.mdtFoldHeading,h6.mdtFoldHeading');
      for (var h = 0; h < headingNodes.length; h++) nodes.push(headingNodes[h]);
      for (var m = 0; m < markers.length; m++) nodes.push(markers[m]);
      if (!nodes.length) return;

      for (var i = 0; i < nodes.length; i++) {
        (function (nodeOrMarker) {
          var marker = null;
          var heading = null;

          if (nodeOrMarker && nodeOrMarker.nodeType === 1 && /^H[1-6]$/.test(nodeOrMarker.tagName)) {
            heading = nodeOrMarker;
          } else {
            marker = nodeOrMarker;
            heading = findHeading(marker);
          }
          if (!heading) return;

          // 兼容:如果是通过 h2.mdtFoldHeading 触发,但里面也有 span.mdtFold,
          // 也要把 marker 当作触发点,这样可以读取默认收起并移除 marker。
          if (!marker) {
            marker = heading.querySelector('.mdtFold');
          }

          var headingBlock = getHeadingBlock(heading);
          if (!headingBlock) return;

          if (headingBlock.getAttribute('data-mdtFoldInit') === '1') return;
          headingBlock.setAttribute('data-mdtFoldInit', '1');

          var level = parseInt(String(heading.tagName).slice(1), 10) || 2;
          var headline = heading.querySelector('.mw-headline');

          // Body: 优先折叠“紧跟着”的图标网格(你这个场景就是 h2 + .mdtIconGrid)。
          // 也支持手动标记:<div class="mdtFoldBody">...</div>
          var body = null;
          var nextEl = headingBlock.nextElementSibling;
          if (nextEl && nextEl.classList) {
            if (nextEl.classList.contains('mdtFoldBody') || nextEl.classList.contains('mdtIconGrid')) {
              body = nextEl;
              body.classList.add('mdtFoldBody');
            }
          }

          // 自动模式:把标题后面直到下一个“同级或更高级标题”的所有节点包起来。
          if (!body) {
            body = document.createElement('div');
            body.className = 'mdtFoldBody';

            var node = headingBlock.nextSibling;
            while (node) {
              var next = node.nextSibling;
              var nodeLevel = getHeadingLevelFromNode(node);
              if (nodeLevel !== null && nodeLevel <= level) break;
              body.appendChild(node);
              node = next;
            }

            headingBlock.parentNode.insertBefore(body, node);
          }

          // Toggle button
          var btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'mdtFoldToggle';
          btn.setAttribute('aria-label', '展开/收起');
          btn.setAttribute('aria-expanded', 'true');
          btn.title = '展开/收起';
          btn.textContent = '\u25BE';

          // Inline style fallback: in case Timeless.css is not loaded yet.
          btn.style.width = '1.6rem';
          btn.style.height = '1.6rem';
          btn.style.marginLeft = '0.25rem';
          btn.style.border = '0';
          btn.style.padding = '0';
          btn.style.borderRadius = '10px';
          btn.style.display = 'inline-flex';
          btn.style.alignItems = 'center';
          btn.style.justifyContent = 'center';
          btn.style.background = 'rgba(0, 175, 137, 0.10)';
          btn.style.color = 'var(--mdtBrand)';
          btn.style.cursor = 'pointer';
          btn.style.fontFamily = 'inherit';
          btn.style.fontSize = '16px';
          btn.style.fontWeight = '700';
          btn.style.lineHeight = '1';
          btn.style.boxShadow = 'none';
          btn.style.outline = 'none';
          btn.style.appearance = 'none';
          btn.style.webkitAppearance = 'none';

          if (headline) {
            headline.classList.add('mdtFoldHeadline');
            headline.appendChild(btn);
          } else {
            var wrap = document.createElement('span');
            wrap.className = 'mdtFoldHeadline';

            while (heading.firstChild) {
              wrap.appendChild(heading.firstChild);
            }

            heading.appendChild(wrap);
            wrap.appendChild(btn);
            headline = wrap;
          }

          // Marker/heading default state
          var defaultClosed = false;
          if (marker && marker.classList && marker.classList.contains('mdtFoldClosed')) {
            defaultClosed = true;
          }
          if (heading.classList && heading.classList.contains('mdtFoldClosed')) {
            defaultClosed = true;
          }

          // Remove marker to avoid extra spacing
          if (marker && marker.parentNode) marker.parentNode.removeChild(marker);

          var page = (mw.config.get('wgPageName') || '').replace(/ /g, '_');
          var id = '';
          if (headline && headline.id) id = headline.id;
          if (!id) id = (heading.textContent || '').trim();
          var storageKey = page + '::' + id;

          function setCollapsed(collapsed, persist) {
            headingBlock.classList.toggle('mdtFoldCollapsed', collapsed);
            btn.setAttribute('aria-expanded', String(!collapsed));
            btn.textContent = collapsed ? '\u25B8' : '\u25BE';
            if (body) {
              body.classList.toggle('mdtFoldHidden', collapsed);
              // Ensure it works even if CSS is missing/cached.
              body.style.display = collapsed ? 'none' : '';
            }
            if (persist) {
              store[storageKey] = collapsed;
              saveStore();
            }
          }

          var collapsed = false;
          if (typeof store[storageKey] === 'boolean') {
            collapsed = store[storageKey];
          } else if (defaultClosed) {
            collapsed = true;
          }

          setCollapsed(collapsed, false);

          btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            ev.stopPropagation();
            var isCollapsed = headingBlock.classList.contains('mdtFoldCollapsed');
            setCollapsed(!isCollapsed, true);
          });
        })(nodes[i]);
      }
    }

    function runOnPage() {
      initFoldSections(document.getElementById('mw-content-text') ||
        document.querySelector('.mw-parser-output') ||
        document.body);
    }

    // MediaWiki 推荐:内容渲染/替换后触发(兼容预览、部分皮肤/插件)。
    if (mw.hook && window.jQuery) {
      mw.hook('wikipage.content').add(function ($content) {
        initFoldSections($content && $content[0] ? $content[0] : null);
      });
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', runOnPage);
    } else {
      runOnPage();
    }
  });
})();


(function () {
  'use strict';

  function onReady(fn) {
    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', fn);
    } else {
      fn();
    }
  }

  function el(tag, cls) {
    var n = document.createElement(tag);
    if (cls) n.className = cls;
    return n;
  }

  function svgIcon(pathD) {
    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('viewBox', '0 0 24 24');
    var p = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    p.setAttribute('d', pathD);
    svg.appendChild(p);
    return svg;
  }

  function findPortletUl(idCandidates) {
    for (var i = 0; i < idCandidates.length; i++) {
      var p = document.getElementById(idCandidates[i]);
      if (!p) continue;
      var ul = p.querySelector('.mw-portlet-body > ul') || p.querySelector('ul');
      if (ul) return ul;
    }
    return null;
  }

  function clonePortlet(title, ul) {
    var wrap = el('div', 'mdtDockSection');
    var h = el('div', 'mdtDockSectionTitle');
    h.textContent = title;
    wrap.appendChild(h);

    var cloned = ul.cloneNode(true);
    // 清掉某些 id,避免重复
    cloned.removeAttribute('id');
    wrap.appendChild(cloned);
    return wrap;
  }

  function initDock() {
    // 可选:只在手机 / 小屏启用。你想全端都启用就删掉这段判断。
    // if (window.matchMedia && window.matchMedia('(min-width: 980px)').matches) return;

    var body = document.body;
    if (!body || body.classList.contains('mdtDockEnabled')) return;
    body.classList.add('mdtDockEnabled');

    var overlay = el('div', 'mdtDockOverlay');
    overlay.addEventListener('click', function () {
      body.classList.remove('mdtDockDrawerOpen');
    });

    var drawer = el('aside', 'mdtDockDrawer');
    var drawerInner = el('div', 'mdtDockDrawerInner');
    drawer.appendChild(drawerInner);

    // 把原本的侧边栏内容“克隆”进抽屉
    // Timeless: p-navigation, p-tb
    // Vector/others: p-navigation, p-tb 也常见
    var nav = findPortletUl(['p-navigation', 'p-Navigation']);
    if (nav) drawerInner.appendChild(clonePortlet('导航', nav));

    var tools = findPortletUl(['p-tb', 'p-Toolbox']);
    if (tools) drawerInner.appendChild(clonePortlet('wiki工具', tools));

    // 折叠树:把 "-- " 前缀的条目变成可折叠子项
    function buildNavTree(ulRoot) {
      if (!ulRoot) return;
      if (ulRoot.getAttribute('data-mdtNav') === '1') return;
      ulRoot.setAttribute('data-mdtNav', '1');

      var KEY = 'mdtNavTreeOpen';
      var store = {};
      try {
        store = JSON.parse(localStorage.getItem(KEY) || '{}');
      } catch (e) {
        store = {};
      }

      function getKey(li) {
        if (li && li.id) return li.id;
        var a = li ? li.querySelector('a') : null;
        return a ? (a.getAttribute('href') || '') : '';
      }

      function setStored(li, open) {
        var k = getKey(li);
        if (!k) return;
        store[k] = open;
        try {
          localStorage.setItem(KEY, JSON.stringify(store));
        } catch (e) {}
      }

      function setOpen(li, open, persist) {
        li.classList.toggle('mdtNavOpen', open);
        if (persist) setStored(li, open);
      }

      function ensureGroup(li) {
        li.classList.add('mdtNavGroup');

        var sub = li.querySelector('ul.mdtNavSublist');
        if (!sub) {
          sub = document.createElement('ul');
          sub.className = 'mdtNavSublist';
          li.appendChild(sub);
        }

        var btn = li.querySelector('button.mdtNavToggle');
        if (!btn) {
          btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'mdtNavToggle';
          btn.setAttribute('aria-label', '展开/收起');
          btn.setAttribute('aria-expanded', 'false');
          li.insertBefore(btn, sub);

          btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            ev.stopPropagation();
            var isOpen = li.classList.contains('mdtNavOpen');
            setOpen(li, !isOpen, true);
            btn.setAttribute('aria-expanded', String(!isOpen));
          });
        }

        var k = getKey(li);
        if (store[k] === true) {
          li.classList.add('mdtNavOpen');
          btn.setAttribute('aria-expanded', 'true');
        }

        return sub;
      }

      var items = Array.prototype.slice.call(ulRoot.children);
      var lastAtDepth = [];
      for (var i = 0; i < items.length; i++) {
        var li = items[i];
        if (!li || li.tagName !== 'LI') continue;

        var a = li.querySelector('a');
        if (!a) continue;

        var raw = (a.textContent || '').trim();
        var m = raw.match(/^(-{2,})\s*(.*)$/);
        var depth = 0;
        if (m) {
          depth = Math.min(Math.floor(m[1].length / 2), 3);
          a.textContent = m[2];
        }

        if (depth > 0 && lastAtDepth[depth - 1]) {
          li.classList.add('mdtNavSubItem');
          li.classList.add('mdtNavDepth' + String(depth));
          var sublist = ensureGroup(lastAtDepth[depth - 1]);
          sublist.appendChild(li);
        } else {
          depth = 0;
        }

        lastAtDepth[depth] = li;
        lastAtDepth.length = depth + 1;
      }

      // 当前页面高亮
      try {
        if (typeof mw !== 'undefined' && mw.config) {
          var current = (mw.config.get('wgPageName') || '').split(' ').join('_');
          var links = ulRoot.querySelectorAll('a');
          for (var j = 0; j < links.length; j++) {
            var href = links[j].getAttribute('href') || '';
            if (!href) continue;
            if (href.indexOf('title=') !== -1 && typeof mw.util !== 'undefined') {
              var t = mw.util.getParamValue('title', href);
              if (t && t.split(' ').join('_') === current) {
                links[j].classList.add('mdtDockActiveLink');
                break;
              }
            }
          }
        }
      } catch (e) {}
    }

    // 对抽屉中的导航/工具分别构建折叠树
    var drawerSections = drawerInner.querySelectorAll('.mdtDockSection > ul');
    for (var s = 0; s < drawerSections.length; s++) {
      buildNavTree(drawerSections[s]);
    }

    // 你也可以把其它 portlet 加进来,例如:
    // var lang = findPortletUl(['p-lang']);
    // if (lang) drawerInner.appendChild(clonePortlet('语言', lang));

    // 左侧图标栏
    var dock = el('nav', 'mdtDock');

    // 图标:简单用 SVG path(不依赖字体/图片)
    // home
    var btnHome = el('a', 'mdtDockBtn');
    btnHome.href = '/';
    btnHome.title = '首页';
    btnHome.appendChild(svgIcon('M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z'));
    dock.appendChild(btnHome);

    // search(点击聚焦搜索框)
    var btnSearch = el('button', 'mdtDockBtn');
    btnSearch.type = 'button';
    btnSearch.title = '搜索';
    btnSearch.appendChild(svgIcon('M15.5 14h-.79l-.28-.27A6.471 6.471 0 0016 9.5 6.5 6.5 0 109.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zM9.5 14C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z'));
    btnSearch.addEventListener('click', function () {
      // Timeless 常见 #searchInput,Vector 常见 input[name=search]
      var input = document.getElementById('searchInput') ||
        document.querySelector('input[name=search]') ||
        document.querySelector('#searchform input[type=search]');
      if (input) input.focus();
    });
    dock.appendChild(btnSearch);

    // menu(打开抽屉)
    var btnMenu = el('button', 'mdtDockBtn mdtDockBtnActive');
    btnMenu.type = 'button';
    btnMenu.title = '导航菜单';
    btnMenu.appendChild(svgIcon('M3 6h18v2H3V6zm0 5h18v2H3v-2zm0 5h18v2H3v-2z'));
    btnMenu.addEventListener('click', function () {
      body.classList.toggle('mdtDockDrawerOpen');
    });
    dock.appendChild(btnMenu);

    dock.appendChild(el('div', 'mdtDockSpacer'));

    // settings(先做占位:你以后可做主题切换等)
    var btnSettings = el('button', 'mdtDockBtn');
    btnSettings.type = 'button';
    btnSettings.title = '设置';
    btnSettings.appendChild(svgIcon('M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.11-.2-.36-.28-.57-.2l-2.39.96c-.5-.38-1.04-.69-1.64-.92l-.36-2.54A.488.488 0 0014.41 2h-3.82c-.24 0-.44.17-.48.41l-.36 2.54c-.6.23-1.14.54-1.64.92l-2.39-.96c-.21-.08-.46 0-.57.2L2.23 8.43c-.11.2-.06.46.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58a.5.5 0 00-.12.61l1.92 3.32c.11.2.36.28.57.2l2.39-.96c.5.38 1.04.69 1.64.92l.36 2.54c.04.24.24.41.48.41h3.82c.24 0 .44-.17.48-.41l.36-2.54c.6-.23 1.14-.54 1.64-.92l2.39.96c.21.08.46 0 .57-.2l1.92-3.32a.5.5 0 00-.12-.61l-2.03-1.58zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5S10.07 8.5 12 8.5s3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z'));
    btnSettings.addEventListener('click', function () {
      alert('这里可以做:主题切换/字号等');
    });
    dock.appendChild(btnSettings);

    // 组装
    document.body.appendChild(overlay);
    document.body.appendChild(drawer);
    document.body.appendChild(dock);

    // Esc 关闭抽屉
    document.addEventListener('keydown', function (ev) {
      if (ev.key === 'Escape') {
        body.classList.remove('mdtDockDrawerOpen');
      }
    });

    // 注意:如果你已经在 Common.js 里有同名的折叠树脚本,也没关系,
    // 这里对 clone 的 ulRoot 设置了 data-mdtNav=1,避免重复处理。
  }

  onReady(initDock);
})();