MediaWiki:Common.js

来自Mindustry中文wiki
绿豆留言 | 贡献2026年1月23日 (五) 14:44的版本

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Internet Explorer或Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
  • Opera:Ctrl-F5
/* 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';

  /* Timeless: 左/右侧栏抽屉 + 首页按钮 */
  mw.loader.using(['mediawiki.util'], function () {
    if (mw.config.get('skin') !== 'timeless') return;

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

    var ICON_HOME =
      '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>';

    var ICON_MENU =
      '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/></svg>';

    var ICON_GEAR =
      '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19.14 12.94c.04-.31.06-.63.06-.94s-.02-.63-.06-.94l2.03-1.58a.5.5 0 0 0 .12-.64l-1.92-3.32a.5.5 0 0 0-.6-.22l-2.39.96c-.5-.38-1.04-.7-1.63-.94l-.36-2.54A.5.5 0 0 0 13.9 2h-3.8a.5.5 0 0 0-.49.42l-.36 2.54c-.59.24-1.13.56-1.63.94l-2.39-.96a.5.5 0 0 0-.6.22L2.71 8.48a.5.5 0 0 0 .12.64l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94L2.83 14.52a.5.5 0 0 0-.12.64l1.92 3.32c.13.22.39.31.6.22l2.39-.96c.5.38 1.04.7 1.63.94l.36 2.54c.05.24.25.42.49.42h3.8c.24 0 .44-.18.49-.42l.36-2.54c.59-.24 1.13-.56 1.63-.94l2.39.96c.22.09.47 0 .6-.22l1.92-3.32a.5.5 0 0 0-.12-.64l-2.03-1.58zM12 15.5A3.5 3.5 0 1 1 12 8.5a3.5 3.5 0 0 1 0 7z"/></svg>';

    function wrapIcon(svg) {
      var span = document.createElement('span');
      span.className = 'mdtDockIcon';
      span.innerHTML = svg;
      return span;
    }

    onReady(function () {
      if (document.getElementById('mdtDockLeft')) return;

      var siteNav = document.getElementById('mw-site-navigation');
      var relatedNav = document.getElementById('mw-related-navigation');
      if (!siteNav && !relatedNav) return;

      document.body.classList.add('mdtDockEnabled');

      var backdrop = document.createElement('div');
      backdrop.id = 'mdtDockBackdrop';

      function syncBackdrop() {
        var open =
          document.body.classList.contains('mdtDockNavOpen') ||
          document.body.classList.contains('mdtDockToolsOpen');
        backdrop.classList.toggle('is-active', open);
      }

      function closeAll() {
        document.body.classList.remove('mdtDockNavOpen', 'mdtDockToolsOpen');
        syncBackdrop();
      }

      backdrop.addEventListener('click', closeAll);
      document.addEventListener('keydown', function (e) {
        if (e.key === 'Escape') closeAll();
      });

      // 左侧:홈 + 汉堡
      var left = document.createElement('div');
      left.id = 'mdtDockLeft';

      var home = document.createElement('a');
      home.className = 'mdtDockBtn';
      home.href = mw.util.getUrl('首页'); 
      home.title = '首页';
      home.setAttribute('aria-label', '首页');
      home.appendChild(wrapIcon(ICON_HOME));
      left.appendChild(home);

      if (siteNav) {
        var navBtn = document.createElement('button');
        navBtn.type = 'button';
        navBtn.className = 'mdtDockBtn';
        navBtn.title = '导航';
        navBtn.setAttribute('aria-label', '导航');
        navBtn.appendChild(wrapIcon(ICON_MENU));
        navBtn.addEventListener('click', function () {
          var nowOpen = document.body.classList.toggle('mdtDockNavOpen');
          if (nowOpen) document.body.classList.remove('mdtDockToolsOpen');
          syncBackdrop();
        });
        left.appendChild(navBtn);
      }

// 右侧:齿轮
var right = document.createElement('div');
right.id = 'mdtDockRight';

if (relatedNav) {
  var toolsBtn = document.createElement('button');
  toolsBtn.type = 'button';
  toolsBtn.className = 'mdtDockBtn';
  toolsBtn.title = '页面工具';
  toolsBtn.setAttribute('aria-label', '页面工具');
  toolsBtn.appendChild(wrapIcon(ICON_GEAR));
  toolsBtn.addEventListener('click', function () {
    var nowOpen = document.body.classList.toggle('mdtDockToolsOpen');
    if (nowOpen) document.body.classList.remove('mdtDockNavOpen');
    syncBackdrop();
  });
  left.appendChild(toolsBtn);
}

document.body.appendChild(backdrop);
document.body.appendChild(left);


      document.body.appendChild(backdrop);
      document.body.appendChild(left);
      document.body.appendChild(right);
      syncBackdrop();
    });
  });
})();

(function () {
  'use strict';

  function isTimeless() {
    try {
      return String(mw.config.get('skin') || '').toLowerCase() === 'timeless';
    } catch (e) {
      return false;
    }
  }

  function isViewMode() {
    var a = String(mw.config.get('wgAction') || 'view').toLowerCase();
    return a === 'view';
  }

  function isMobileUA() {
    var ua = String(navigator.userAgent || '');
    return /Mobi|Android|iPhone|iPad|iPod/i.test(ua);
  }

  function normalizeText(text) {
    return String(text || '')
      .replace(/\s+/g, ' ')
      .replace(/[\u25B8\u25BE]/g, '')
      .trim();
  }

  function collectGroups(root) {
    var nodes = root.querySelectorAll('h2, h3');
    var groups = [];
    var current = null;

    for (var i = 0; i < nodes.length; i++) {
      var h = nodes[i];
      if (!h) continue;

      var id = h.id || '';
      if (!id) continue;

      var text = normalizeText(h.textContent);
      if (!text) continue;

      if (h.tagName === 'H2') {
        current = { id: id, text: text, children: [] };
        groups.push(current);
      } else {
        if (!current) continue;
        current.children.push({ id: id, text: text });
      }
    }

    return groups;
  }

  function flattenIds(groups) {
    var ids = [];
    for (var i = 0; i < groups.length; i++) {
      ids.push(groups[i].id);
      var children = groups[i].children || [];
      for (var j = 0; j < children.length; j++) {
        ids.push(children[j].id);
      }
    }
    return ids;
  }

  function buildPanel(groups) {
    var wrap = document.createElement('div');
    wrap.id = 'mdtFloatToc';
    wrap.className = 'mdtFloatToc';

    wrap.innerHTML =
      "<button type='button' class='mdtFloatTocBtn' aria-label='目录'>≡</button>" +
      "<div class='mdtFloatTocPanel' role='dialog' aria-label='目录'>" +
      "  <div class='mdtFloatTocHead'>" +
      "    <button type='button' class='mdtFloatTocTop'><span aria-hidden='true'>↑</span>返回顶部</button>" +
      "    <div class='mdtFloatTocTitle'>目录</div>" +
      "    <button type='button' class='mdtFloatTocClose' aria-label='关闭'>✕</button>" +
      "  </div>" +
      "  <ul class='mdtFloatTocList'></ul>" +
      "</div>";

    var ul = wrap.querySelector('.mdtFloatTocList');
    for (var i = 0; i < groups.length; i++) {
      (function (g) {
        var li = document.createElement('li');
        li.className = 'mdtFloatTocItem mdtFloatTocGroup';

        var row = document.createElement('div');
        row.className = 'mdtFloatTocRow';

        var a = document.createElement('a');
        a.href = '#' + encodeURIComponent(g.id).replace(/%2F/g, '/');
        a.textContent = g.text;
        row.appendChild(a);

        if (g.children && g.children.length) {
          var btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'mdtFloatTocGroupToggle';
          btn.setAttribute('aria-label', '展开/收起');
          btn.setAttribute('aria-expanded', 'false');
          btn.textContent = '▸';
          row.appendChild(btn);

          var sub = document.createElement('ul');
          sub.className = 'mdtFloatTocSublist';

          for (var j = 0; j < g.children.length; j++) {
            var c = g.children[j];
            var cli = document.createElement('li');
            cli.className = 'mdtFloatTocItem';
            cli.innerHTML = "<a href='#" + encodeURIComponent(c.id).replace(/%2F/g, '/') + "'>" +
              mw.html.escape(c.text) + "</a>";
            sub.appendChild(cli);
          }

          li.appendChild(row);
          li.appendChild(sub);

          btn.addEventListener('click', function (ev) {
            ev.preventDefault();
            ev.stopPropagation();
            var open = li.classList.toggle('mdtFloatTocGroupOpen');
            btn.setAttribute('aria-expanded', String(open));
            btn.textContent = open ? '▾' : '▸';
          });
        } else {
          li.appendChild(row);
        }

        ul.appendChild(li);
      })(groups[i]);
    }

    function openPanel(open) {
      wrap.classList.toggle('mdtFloatTocOpen', open);
    }

    wrap.querySelector('.mdtFloatTocBtn').addEventListener('click', function () {
      openPanel(true);
    });

    wrap.querySelector('.mdtFloatTocClose').addEventListener('click', function () {
      openPanel(false);
    });

    wrap.querySelector('.mdtFloatTocTop').addEventListener('click', function () {
      openPanel(false);
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    // Click a toc item -> close + smooth scroll
    wrap.addEventListener('click', function (ev) {
      var a = ev.target && ev.target.closest ? ev.target.closest('a') : null;
      if (!a) return;
      var href = a.getAttribute('href') || '';
      if (href.charAt(0) !== '#') return;

      ev.preventDefault();
      openPanel(false);

      var id = decodeURIComponent(href.slice(1));
      var target = document.getElementById(id);
      if (target) {
        target.scrollIntoView({ behavior: 'smooth', block: 'start' });
        history.replaceState(null, '', '#' + encodeURIComponent(id));
      }
    });

    // Close when tapping outside
    document.addEventListener('click', function (ev) {
      if (!wrap.classList.contains('mdtFloatTocOpen')) return;
      if (wrap.contains(ev.target)) return;
      openPanel(false);
    });

    // Highlight active heading
    var linkById = {};
    var links = wrap.querySelectorAll('a');
    for (var k = 0; k < links.length; k++) {
      var id2 = decodeURIComponent((links[k].getAttribute('href') || '').slice(1));
      linkById[id2] = links[k];
    }

    var ids = flattenIds(groups);
    var ticking = false;
    function updateActive() {
      ticking = false;

      var bestId = '';
      var bestTop = -Infinity;
      for (var i2 = 0; i2 < ids.length; i2++) {
        var el = document.getElementById(ids[i2]);
        if (!el) continue;
        var rect = el.getBoundingClientRect();
        if (rect.top <= 120 && rect.top > bestTop) {
          bestTop = rect.top;
          bestId = ids[i2];
        }
      }

      for (var idKey in linkById) {
        linkById[idKey].classList.toggle('mdtFloatTocActive', idKey === bestId);
      }
    }

    window.addEventListener('scroll', function () {
      if (ticking) return;
      ticking = true;
      window.requestAnimationFrame(updateActive);
    }, { passive: true });

    return wrap;
  }

  function ensureHeadingIds(root) {
    var headings = root.querySelectorAll('h2, h3');
    for (var i = 0; i < headings.length; i++) {
      var h = headings[i];
      if (h.id) continue;
      var hl = h.querySelector('.mw-headline');
      if (hl && hl.id) h.id = hl.id;
    }
  }

  function initFloatToc($content) {
    if (!isTimeless() || !isViewMode() || !isMobileUA()) return;

    var content = $content && $content[0] ? $content[0] : null;
    var root = content || document.querySelector('.mw-parser-output') || document.getElementById('mw-content-text');
    if (!root) return;

    if (document.getElementById('mdtFloatToc')) return;

    ensureHeadingIds(root);
    var groups = collectGroups(root);
    if (!groups.length) return;

    document.body.appendChild(buildPanel(groups));
  }

  mw.loader.using(['mediawiki.util', 'mediawiki.html']).then(function () {
    if (mw.hook && window.jQuery) {
      mw.hook('wikipage.content').add(initFloatToc);
    }
    initFloatToc(null);
  });
})();

/*
  MDT Float TOC (desktop: dock to top-right gutter, default open)
  Paste this whole file into: MediaWiki:Common.js (very bottom)
*/
(function () {
  'use strict';

  var DESKTOP_MIN_WIDTH = 1000;
  var GAP_PX = 12;
  var FALLBACK_TOP_REM = 4.1;

  function isViewMode() {
    try {
      return String(mw.config.get('wgAction') || 'view').toLowerCase() === 'view';
    } catch (e) {
      return true;
    }
  }

  function isDesktop() {
    try {
      return (
        window.matchMedia &&
        window.matchMedia('(min-width: ' + String(DESKTOP_MIN_WIDTH) + 'px)').matches
      );
    } catch (e) {
      return false;
    }
  }

  function remToPx(rem) {
    var fs = 16;
    try {
      fs = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
    } catch (e) {}
    return rem * fs;
  }

  function getDockTopPx() {
    var dock = document.getElementById('mdtDockLeft') || document.getElementById('mdtDockRight');
    if (dock && dock.getBoundingClientRect) {
      return Math.round(dock.getBoundingClientRect().top);
    }
    return Math.round(remToPx(FALLBACK_TOP_REM));
  }

  function normalizeText(text) {
    return String(text || '')
      .replace(/\s+/g, ' ')
      .replace(/[\u25B8\u25BE]/g, '')
      .trim();
  }

  function ensureHeadingIds(root) {
    var headings = root.querySelectorAll('h2, h3');
    for (var i = 0; i < headings.length; i++) {
      var h = headings[i];
      if (!h || h.id) continue;
      var hl = h.querySelector('.mw-headline');
      if (hl && hl.id) h.id = hl.id;
    }
  }

  function shouldSkipHeading(h) {
    if (!h) return true;
    if (!h.id) return true;
    if (h.closest('#mw-site-navigation, #mw-related-navigation, .mw-portlet, .mdtRightRail')) return true;
    return false;
  }

  function collectGroups(root) {
    var nodes = root.querySelectorAll('h2, h3');
    var groups = [];
    var current = null;

    for (var i = 0; i < nodes.length; i++) {
      var h = nodes[i];
      if (shouldSkipHeading(h)) continue;

      var id = h.id;
  var headline = h.querySelector('.mw-headline');
  var text = normalizeText(headline ? headline.textContent : h.textContent);
      if (!text) continue;

      if (h.tagName === 'H2') {
        current = { id: id, text: text, children: [] };
        groups.push(current);
      } else {
        if (!current) continue;
        current.children.push({ id: id, text: text });
      }
    }

    return groups;
  }

  function flattenIds(groups) {
    var ids = [];
    for (var i = 0; i < groups.length; i++) {
      ids.push(groups[i].id);
      var children = groups[i].children || [];
      for (var j = 0; j < children.length; j++) ids.push(children[j].id);
    }
    return ids;
  }

  function injectCssOnce() {
    if (document.getElementById('mdtFloatTocStyle')) return;

    var css = [
      '#mw-content-text .mw-headline{scroll-margin-top:5rem;}',

      /* wrapper (mobile default: bottom-right) */
      '#mdtFloatToc{position:fixed;right:0.9rem;bottom:1.1rem;top:auto;z-index:9999;font-family:inherit;display:block !important;}',

      /* open button */
      '#mdtFloatToc .mdtFloatTocBtn{width:3rem;height:3rem;border-radius:999px;border:1px solid rgba(255,255,255,0.14);background:rgba(0,0,0,0.70);color:rgba(255,255,255,0.92);box-shadow:0 12px 28px rgba(0,0,0,0.28);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-weight:900;}',
      '#mdtFloatToc .mdtFloatTocBtn:hover{border-color:rgba(0,207,160,0.60);box-shadow:0 12px 28px rgba(0,0,0,0.28),0 0 0 3px rgba(0,207,160,0.18);}',
      '#mdtFloatToc.mdtFloatTocOpen .mdtFloatTocBtn{opacity:0;pointer-events:none;}',

      /* panel (mobile default: popup) */
      '#mdtFloatToc .mdtFloatTocPanel{position:absolute;right:0;bottom:3.75rem;width:min(22rem,88vw);max-height:min(70vh,34rem);overflow:auto;padding:0.85rem 0.9rem;border-radius:16px;background:rgba(36,46,45,0.92);border:1px solid rgba(255,255,255,0.14);box-shadow:0 18px 44px rgba(0,0,0,0.32);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:rgba(255,255,255,0.92);display:none;}',
      '#mdtFloatToc.mdtFloatTocOpen .mdtFloatTocPanel{display:block;}',

      /* head */
      '#mdtFloatToc .mdtFloatTocHead{display:flex;align-items:center;justify-content:space-between;gap:0.75rem;padding:0 0 0.55rem 0;margin:0 0 0.6rem 0;border-bottom:1px solid rgba(255,255,255,0.10);}',
      '#mdtFloatToc .mdtFloatTocTitle{font-weight:900;opacity:0.9;letter-spacing:0.06em;}',
      '#mdtFloatToc .mdtFloatTocTop,#mdtFloatToc .mdtFloatTocClose{border:0;background:transparent;color:inherit;cursor:pointer;padding:0;font-family:inherit;}',
      '#mdtFloatToc .mdtFloatTocTop{display:inline-flex;align-items:center;gap:0.55rem;font-weight:900;color:var(--mdtBrand2,#00cfa0);}',
      '#mdtFloatToc .mdtFloatTocClose{width:2rem;height:2rem;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;background:rgba(255,255,255,0.06);}',
      '#mdtFloatToc .mdtFloatTocClose:hover{background:rgba(255,255,255,0.12);}',

      /* list */
      '#mdtFloatToc .mdtFloatTocList{list-style:none;margin:0;padding:0 0 0 0.85rem;border-left:1px solid rgba(255,255,255,0.12);}',
      '#mdtFloatToc .mdtFloatTocRow{display:flex;align-items:center;gap:0.35rem;}',
      '#mdtFloatToc .mdtFloatTocItem{margin:0.18rem 0;}',
      '#mdtFloatToc .mdtFloatTocItem a,#mdtFloatToc .mdtFloatTocItem a:visited{color:rgba(255,255,255,0.86);text-decoration:none;font-weight:750;display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}',
      '#mdtFloatToc .mdtFloatTocItem a:hover{color:var(--mdtBrand2,#00cfa0);text-decoration:underline;}',
      '#mdtFloatToc .mdtFloatTocItem a.mdtFloatTocActive{color:var(--mdtBrand2,#00cfa0);text-decoration:underline;}',

      /* sub list */
      '#mdtFloatToc .mdtFloatTocGroupToggle{border:0;background:rgba(255,255,255,0.06);color:rgba(255,255,255,0.80);cursor:pointer;padding:0;width:2rem;height:2rem;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;}',
      '#mdtFloatToc .mdtFloatTocGroupToggle:hover{background:rgba(255,255,255,0.12);}',
      '#mdtFloatToc .mdtFloatTocGroup.mdtFloatTocGroupOpen .mdtFloatTocGroupToggle{color:var(--mdtBrand2,#00cfa0);}',
      '#mdtFloatToc .mdtFloatTocSublist{list-style:none;margin:0.35rem 0 0.25rem;padding:0 0 0 0.85rem;border-left:1px solid rgba(255,255,255,0.12);display:none;}',
      '#mdtFloatToc .mdtFloatTocGroup.mdtFloatTocGroupOpen .mdtFloatTocSublist{display:block;}'
    ].join('\n');

    var style = document.createElement('style');
    style.id = 'mdtFloatTocStyle';
    style.type = 'text/css';
    style.textContent = css;
    document.head.appendChild(style);
  }

  function applyDesktopDock(wrap) {
    if (!wrap) return;
    var panel = wrap.querySelector('.mdtFloatTocPanel');
    if (!panel) return;

    if (!isDesktop()) {
      wrap.style.top = '';
      wrap.style.right = '';
      wrap.style.bottom = '';
      panel.style.position = '';
      panel.style.top = '';
      panel.style.right = '';
      panel.style.bottom = '';
      panel.style.left = '';
      panel.style.width = '';
      panel.style.maxHeight = '';
      return;
    }

    var topPx = getDockTopPx();
    wrap.style.top = String(topPx) + 'px';
    wrap.style.right = String(GAP_PX) + 'px';
    wrap.style.bottom = 'auto';

    // Fit into the right gutter: don't cover the white content card
    var content = document.getElementById('mw-content') || document.getElementById('content');
    if (!content || !content.getBoundingClientRect) return;

    var rect = content.getBoundingClientRect();
    var leftPx = Math.round(rect.right + GAP_PX);
    var available = Math.round(window.innerWidth - leftPx - GAP_PX);

    // No right gutter -> keep mobile popup style
    if (available < 220) return;

  panel.style.position = 'fixed';
  panel.style.top = String(topPx) + 'px';
  panel.style.right = String(GAP_PX) + 'px';
  panel.style.bottom = 'auto';
  panel.style.left = String(leftPx) + 'px';
  panel.style.width = 'auto';
  panel.style.maxHeight = 'calc(100vh - ' + String(topPx + GAP_PX) + 'px)';

    // Default open once per page load
    if (wrap.getAttribute('data-mdtDefaultOpen') !== '1') {
      wrap.classList.add('mdtFloatTocOpen');
      wrap.setAttribute('data-mdtDefaultOpen', '1');
    }
  }

  function buildPanel(groups) {
    injectCssOnce();

    var wrap = document.createElement('div');
    wrap.id = 'mdtFloatToc';
    wrap.className = 'mdtFloatToc';

    var openBtn = document.createElement('button');
    openBtn.type = 'button';
    openBtn.className = 'mdtFloatTocBtn';
    openBtn.setAttribute('aria-label', '\u76ee\u5f55');
    openBtn.textContent = '\u2261';
    wrap.appendChild(openBtn);

    var panel = document.createElement('div');
    panel.className = 'mdtFloatTocPanel';
    panel.setAttribute('role', 'dialog');
    panel.setAttribute('aria-label', '\u76ee\u5f55');
    wrap.appendChild(panel);

    var head = document.createElement('div');
    head.className = 'mdtFloatTocHead';
    panel.appendChild(head);

    var topBtn = document.createElement('button');
    topBtn.type = 'button';
    topBtn.className = 'mdtFloatTocTop';
    topBtn.textContent = '\u2191 \u8fd4\u56de\u9876\u90e8';
    head.appendChild(topBtn);

    var title = document.createElement('div');
    title.className = 'mdtFloatTocTitle';
    title.textContent = '\u76ee\u5f55';
    head.appendChild(title);

    var closeBtn = document.createElement('button');
    closeBtn.type = 'button';
    closeBtn.className = 'mdtFloatTocClose';
    closeBtn.setAttribute('aria-label', '\u5173\u95ed');
    closeBtn.textContent = 'X';
    head.appendChild(closeBtn);

    var ul = document.createElement('ul');
    ul.className = 'mdtFloatTocList';
    panel.appendChild(ul);

    function openPanel(open) {
      wrap.classList.toggle('mdtFloatTocOpen', open);
    }

    openBtn.addEventListener('click', function () {
      openPanel(true);
      applyDesktopDock(wrap);
    });

    closeBtn.addEventListener('click', function () {
      openPanel(false);
    });

    topBtn.addEventListener('click', function () {
      if (!isDesktop()) openPanel(false);
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    for (var i = 0; i < groups.length; i++) {
      (function (g) {
        var li = document.createElement('li');
        li.className = 'mdtFloatTocItem mdtFloatTocGroup';

        var row = document.createElement('div');
        row.className = 'mdtFloatTocRow';
        li.appendChild(row);

        var a = document.createElement('a');
        a.href = '#' + encodeURIComponent(g.id).replace(/%2F/g, '/');
        a.textContent = g.text;
        row.appendChild(a);

        if (g.children && g.children.length) {
          var toggle = document.createElement('button');
          toggle.type = 'button';
          toggle.className = 'mdtFloatTocGroupToggle';
          toggle.setAttribute('aria-label', '\u5c55\u5f00/\u6536\u8d77');
          toggle.setAttribute('aria-expanded', 'false');
          toggle.textContent = '>';
          row.appendChild(toggle);

          var sub = document.createElement('ul');
          sub.className = 'mdtFloatTocSublist';
          li.appendChild(sub);

          for (var j = 0; j < g.children.length; j++) {
            var c = g.children[j];
            var cli = document.createElement('li');
            cli.className = 'mdtFloatTocItem';
            var ca = document.createElement('a');
            ca.href = '#' + encodeURIComponent(c.id).replace(/%2F/g, '/');
            ca.textContent = c.text;
            cli.appendChild(ca);
            sub.appendChild(cli);
          }

          toggle.addEventListener('click', function (ev) {
            ev.preventDefault();
            ev.stopPropagation();
            var open = li.classList.toggle('mdtFloatTocGroupOpen');
            toggle.setAttribute('aria-expanded', String(open));
            toggle.textContent = open ? 'v' : '>';
          });
        }

        ul.appendChild(li);
      })(groups[i]);
    }

    // Click a toc item -> smooth scroll
    wrap.addEventListener('click', function (ev) {
      var target = ev.target;
      if (!target) return;
      var a = target.closest ? target.closest('a') : null;
      if (!a) return;

      var href = a.getAttribute('href') || '';
      if (href.charAt(0) !== '#') return;

      ev.preventDefault();
      if (!isDesktop()) openPanel(false);

      var id = decodeURIComponent(href.slice(1));
      var el = document.getElementById(id);
      if (el) {
        el.scrollIntoView({ behavior: 'smooth', block: 'start' });
        try {
          history.replaceState(null, '', '#' + encodeURIComponent(id));
        } catch (e) {}
      }
    });

    // Mobile only: tap outside to close. Desktop: do nothing.
    document.addEventListener('click', function (ev) {
      if (isDesktop()) return;
      if (!wrap.classList.contains('mdtFloatTocOpen')) return;
      if (wrap.contains(ev.target)) return;
      openPanel(false);
    });

    // Highlight active heading
    var linkById = {};
    var links = wrap.querySelectorAll('a');
    for (var k = 0; k < links.length; k++) {
      var id2 = decodeURIComponent((links[k].getAttribute('href') || '').slice(1));
      linkById[id2] = links[k];
    }

    var ids = flattenIds(groups);
    var ticking = false;

    function updateActive() {
      ticking = false;

      var bestId = '';
      var bestTop = -Infinity;

      for (var i2 = 0; i2 < ids.length; i2++) {
        var el2 = document.getElementById(ids[i2]);
        if (!el2) continue;
        var rect = el2.getBoundingClientRect();
        if (rect.top <= 120 && rect.top > bestTop) {
          bestTop = rect.top;
          bestId = ids[i2];
        }
      }

      for (var idKey in linkById) {
        linkById[idKey].classList.toggle('mdtFloatTocActive', idKey === bestId);
      }
    }

    window.addEventListener(
      'scroll',
      function () {
        if (ticking) return;
        ticking = true;
        window.requestAnimationFrame(updateActive);
      },
      { passive: true }
    );

    return wrap;
  }

  var resizeBound = false;

  function initFloatToc($content) {
    if (!isViewMode()) return;

    if (document.getElementById('mdtFloatToc')) {
      applyDesktopDock(document.getElementById('mdtFloatToc'));
      return;
    }

    var content = $content && $content[0] ? $content[0] : null;
    var root =
      content || document.querySelector('.mw-parser-output') || document.getElementById('mw-content-text');
    if (!root) return;

    ensureHeadingIds(root);
    var groups = collectGroups(root);
    if (!groups.length) return;

    var wrap = buildPanel(groups);
    document.body.appendChild(wrap);

    applyDesktopDock(wrap);

    if (!resizeBound) {
      resizeBound = true;
      window.addEventListener(
        'resize',
        function () {
          var el = document.getElementById('mdtFloatToc');
          if (el) applyDesktopDock(el);
        },
        { passive: true }
      );
      window.addEventListener('load', function () {
        var el = document.getElementById('mdtFloatToc');
        if (el) applyDesktopDock(el);
      });
    }
  }

  mw.loader.using(['mediawiki.util']).then(function () {
    if (mw.hook && window.jQuery) {
      mw.hook('wikipage.content').add(initFloatToc);
    }

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

/* MDTWIKI_APPEND_ICON_TOOL v20260123
 * Paste this file at the END of MediaWiki:Common.js (do not replace your existing code).
 * Timeless skin only. Works in action=edit (source editor textarea wpTextbox1).
 */
(function () {
  'use strict';

  if (!window.mw) return;
  if (window.__mdtIconEditorToolInit) return;
  window.__mdtIconEditorToolInit = true;

  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') return;

    var MDT_ICON_SIZE = 18;

    // Map: page title (wgPageName format) -> uploaded file name.
    // Add more entries as needed.
    var MDT_ICON_MAP = {
      '\u94dc': 'item-copper-ui.png',
      '\u94c5': 'item-lead-ui.png',
      '\u6c34': 'liquid-water-ui.png',
      '\u521d\u4ee3\u6838\u5fc3': 'block-core-shard-ui.png',

      '\u585e\u666e\u7f57': 'planet-serpulo.png',
      '\u57c3\u91cc\u514b\u5c14': 'planet-erekir.png',

      '\u661f\u8f89': 'unit-flare-ui.png',
      '\u5929\u57a0': 'unit-horizon-ui.png',
      '\u82cd\u7a79': 'unit-zenith-ui.png',

      '\u5c16\u5200': 'Unit-dagger-ui.png',
      '\u65b0\u661f': 'Unit-nova-ui.png',
      '\u722c\u866b': 'Unit-crawler-ui.png',
      '\u8718\u86db': 'Unit-mono-ui.png',
      '\u6a59\u9c7c': 'Unit-risso-ui.png',
      '\u6f5c\u8788': 'Unit-retusa-ui.png',
      '\u56f4\u62a4': 'Unit-stell-ui.png',
      '\u5b88\u5b50': 'Unit-merui-ui.png',
      '\u6324\u8131': 'Unit-elude-ui.png',

      '\u77f3\u58a8\u538b\u7f29\u673a': 'block-graphite-press-ui.png',
      '\u591a\u91cd\u538b\u7f29\u673a': 'block-multi-press-ui.png',
      '\u7845\u51b6\u70bc\u5382': 'block-silicon-smelter-ui.png',
      '\u70ed\u80fd\u5769\u57da': 'block-silicon-crucible-ui.png',
      '\u7535\u5f27\u7845\u7089': 'block-silicon-arc-furnace-ui.png',

      '\u621f\u9cb8': 'unit-bryde-ui.png',
      '\u96f6\u53f7\u5730\u533a': 'sector-groundZero.png',
      '\u51b0\u51bb\u68ee\u6797': 'sector-frozenForest.png'
    };

    function getEditorTextarea() {
      return document.getElementById('wpTextbox1') ||
        document.querySelector('textarea#wpTextbox1');
    }

    function buildIconWikitext(filename, title) {
      var file = String(filename);
      var t = String(title);
      return '<span class="mdtPixelIcon">[[File:' + file + '|' + String(MDT_ICON_SIZE) + 'px|link=' + t + ']]</span>';
    }

    function buildTitlePattern(titles) {
      // Prefer longer first to avoid partial matches.
      var list = titles.slice().sort(function (a, b) { return b.length - a.length; });
      var escaped = [];
      for (var i = 0; i < list.length; i++) {
        escaped.push(list[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
      }
      return escaped.join('|');
    }

    function convertWikitext(text) {
      var titles = Object.keys(MDT_ICON_MAP || {});
      if (!titles.length) return { text: text, changed: 0 };

      var pattern = buildTitlePattern(titles);

      // Only convert links like [[Title]] or [[Title|Label]].
      // Skip links already preceded by mdtPixelIcon span.
      var re = new RegExp('\\[\\[(' + pattern + ')(?:\\|[^\\]]*)?\\]\\]', 'g');
      
      var changed = 0;
      var out = text.replace(re, function (m, title, offset) {
        var before = text.slice(Math.max(0, offset - 120), offset);
        if (before.indexOf('class="mdtPixelIcon"') !== -1) return m;
        if (/\[\[File:|\[\[Image:/i.test(m)) return m;

        var filename = MDT_ICON_MAP[title];
        if (!filename) return m;

        changed++;
        return buildIconWikitext(filename, title) + ' ' + m;
      });

      return { text: out, changed: changed };
    }

    function initEditorTool() {
      var ta = getEditorTextarea();
      if (!ta) return;
      if (ta.getAttribute('data-mdtIconTool') === '1') return;
      ta.setAttribute('data-mdtIconTool', '1');

      var host = document.createElement('div');
      host.style.margin = '0 0 8px 0';
      host.style.display = 'flex';
      host.style.gap = '8px';
      host.style.flexWrap = 'wrap';
      host.style.alignItems = 'center';

      var btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'mw-ui-button mw-ui-progressive';
      btn.textContent = '\u4e00\u952e\u914d\u56fe\u6807';

      var undo = document.createElement('button');
      undo.type = 'button';
      undo.className = 'mw-ui-button';
      undo.textContent = '\u64a4\u9500\u914d\u56fe\u6807';
      undo.disabled = true;

      var note = document.createElement('span');
      note.style.opacity = '0.75';
      note.style.fontSize = '12px';
      note.textContent = '\u53ea\u5904\u7406\u005b\u005b\u002e\u002e\u002e\u005d\u005d\u94fe\u63a5';

      host.appendChild(btn);
      host.appendChild(undo);
      host.appendChild(note);

      if (ta.parentNode) ta.parentNode.insertBefore(host, ta);

      var backup = '';
      btn.addEventListener('click', function () {
        var current = String(ta.value || '');
        var result = convertWikitext(current);
        if (!result.changed) {
          alert('\u6ca1\u6709\u53ef\u66ff\u6362\u7684\u94fe\u63a5');
          return;
        }
        backup = current;
        undo.disabled = false;
        ta.value = result.text;
        alert('\u5df2\u63d2\u5165 ' + String(result.changed) + ' \u4e2a\u56fe\u6807');
      });

      undo.addEventListener('click', function () {
        if (!backup) {
          alert('\u6ca1\u6709\u5907\u4efd');
          return;
        }
        ta.value = backup;
        backup = '';
        undo.disabled = true;
        alert('\u5df2\u64a4\u9500');
      });
    }

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