“MediaWiki:Common.js”的版本间差异

来自RMzas&SMS_Knowledge_Base_Wiki
跳转到导航 跳转到搜索
(将特效更名为鼠标特效,并简化为开启/关闭两个选项)
 
(未显示2个用户的9个中间版本)
第1行: 第1行:
 
/* 这里的任何JavaScript将为所有用户在每次页面载入时加载。 */
 
/* 这里的任何JavaScript将为所有用户在每次页面载入时加载。 */
  
−
// 显示最近更新的页面列表(去重版)
+
/* 阅读主题:仅在当前浏览器保存,未选择时跟随系统。 */
−
// 显示最近更新的页面列表(增强容器定位)
+
(function () {
 +
  'use strict';
 +
 
 +
  var root = document.documentElement;
 +
  var storageKey = 'rmzas-wiki-theme';
 +
  var systemTheme = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
 +
  var selector;
 +
  var mode = 'auto';
 +
 
 +
  // 防止脚本重复执行时添加多个控件或事件监听。
 +
  if (root.hasAttribute('data-rmzas-theme')) return;
 +
 
 +
  function normalize(value) {
 +
    return value === 'light' || value === 'dark' ? value : 'auto';
 +
  }
 +
 
 +
  function applyTheme() {
 +
    var dark = mode === 'dark' || (mode === 'auto' && systemTheme && systemTheme.matches);
 +
    root.setAttribute('data-rmzas-theme', mode);
 +
    root.classList.toggle('rmzas-theme-dark', !!dark);
 +
    if (selector) selector.value = mode;
 +
  }
 +
 
 +
  try {
 +
    mode = normalize(window.localStorage.getItem(storageKey));
 +
  } catch (error) {
 +
    // 存储不可用时仍允许切换,当前页面内保留选择。
 +
  }
 +
  // 不等待 DOM 就绪,尽早应用配色;Common.js 本身仍是异步加载的。
 +
  applyTheme();
 +
 
 +
  if (systemTheme) {
 +
    if (systemTheme.addEventListener) {
 +
      systemTheme.addEventListener('change', applyTheme);
 +
    } else if (systemTheme.addListener) {
 +
      systemTheme.addListener(applyTheme);
 +
    }
 +
  }
 +
 
 +
  window.addEventListener('storage', function (event) {
 +
    if (event.key === storageKey || event.key === null) {
 +
      mode = normalize(event.newValue);
 +
      applyTheme();
 +
    }
 +
  });
 +
 
 +
  function addThemeControl() {
 +
    if (document.getElementById('rmzas-theme-select')) return;
 +
 
 +
    // Vector / MonoBook 的个人工具栏;Timeless 使用用户链接区域。
 +
    var menu = document.querySelector('#p-personal ul, #p-personal .mw-portlet-body ul, #p-personal .body ul, #p-personal-inner ul');
 +
    var control = document.createElement(menu ? 'li' : 'div');
 +
    control.id = 'pt-rmzas-theme';
 +
    control.className = 'rmzas-theme-control';
 +
    if (!menu) control.classList.add('rmzas-theme-control-floating');
 +
 
 +
    var label = document.createElement('label');
 +
    label.htmlFor = 'rmzas-theme-select';
 +
    label.textContent = '主题';
 +
    selector = document.createElement('select');
 +
    selector.id = 'rmzas-theme-select';
 +
    selector.title = '选择阅读主题(此浏览器会记住你的选择)';
 +
 
 +
    [['auto', '跟随系统'], ['light', '浅色'], ['dark', '深色']].forEach(function (item) {
 +
      var option = document.createElement('option');
 +
      option.value = item[0];
 +
      option.textContent = item[1];
 +
      selector.appendChild(option);
 +
    });
 +
    selector.value = mode;
 +
    selector.addEventListener('change', function () {
 +
      mode = normalize(selector.value);
 +
      applyTheme();
 +
      try {
 +
        window.localStorage.setItem(storageKey, mode);
 +
      } catch (error) {
 +
        // 隐私设置禁用存储时不影响配色或原有站点脚本。
 +
      }
 +
    });
 +
 
 +
    control.appendChild(label);
 +
    control.appendChild(selector);
 +
    if (menu) {
 +
      menu.insertBefore(control, menu.firstChild);
 +
    } else {
 +
      document.body.appendChild(control);
 +
    }
 +
  }
 +
 
 +
  if (document.readyState === 'loading') {
 +
    document.addEventListener('DOMContentLoaded', addThemeControl);
 +
  } else {
 +
    addThemeControl();
 +
  }
 +
})();
 +
 
 +
/* BA鼠标光标与点击特效:支持开启/关闭,仅在当前浏览器保存。 */
 +
(function () {
 +
  'use strict';
 +
 
 +
  var root = document.documentElement;
 +
  var storageKey = 'rmzas-wiki-fx';
 +
  var scriptUrl = 'http://rmzas.site/wp-content/themes/twentyfifteen/js/ba-click-fx/ba-click-fx.js';
 +
  var motionQuery = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
 +
  var BAClickFXClass = null;
 +
  var activeInstances = new Set();
 +
  var isPatching = false;
 +
  var selector;
 +
  var mode = 'on'; // 'on' | 'off'
 +
 
 +
  if (root.hasAttribute('data-rmzas-fx')) return;
 +
 
 +
  function normalize(value) {
 +
    if (value === 'off') return 'off';
 +
    if (value === 'on') return 'on';
 +
    // 首次访问未设置时:若操作系统开启了减弱动态效果则默认关闭,否则默认开启
 +
    return (motionQuery && motionQuery.matches) ? 'off' : 'on';
 +
  }
 +
 
 +
  function shouldEnable() {
 +
    return mode === 'on';
 +
  }
 +
 
 +
  // 确保页面中有 #myCanvas 供 BAClickFX 挂载,防止其回退生成独立的顶层 canvas
 +
  function ensureTargetCanvas() {
 +
    var cvs = document.getElementById('myCanvas');
 +
    if (!cvs) {
 +
      cvs = document.createElement('canvas');
 +
      cvs.id = 'myCanvas';
 +
      if (document.body) {
 +
        document.body.appendChild(cvs);
 +
      } else {
 +
        document.addEventListener('DOMContentLoaded', function () {
 +
          if (!document.getElementById('myCanvas') && document.body) {
 +
            document.body.appendChild(cvs);
 +
          }
 +
        });
 +
      }
 +
    }
 +
    return cvs;
 +
  }
 +
 
 +
  function hideAllFxCanvases() {
 +
    var canvases = document.querySelectorAll('canvas');
 +
    for (var i = 0; i < canvases.length; i++) {
 +
      canvases[i].style.setProperty('display', 'none', 'important');
 +
      canvases[i].style.setProperty('visibility', 'hidden', 'important');
 +
    }
 +
  }
 +
 
 +
  function showAllFxCanvases() {
 +
    var canvases = document.querySelectorAll('canvas');
 +
    for (var i = 0; i < canvases.length; i++) {
 +
      canvases[i].style.removeProperty('display');
 +
      canvases[i].style.removeProperty('visibility');
 +
    }
 +
  }
 +
 
 +
  function pauseAll() {
 +
    activeInstances.forEach(function (inst) {
 +
      try {
 +
        if (typeof inst.setPaused === 'function') {
 +
          inst.setPaused(true);
 +
        }
 +
        if (typeof inst.clear === 'function') {
 +
          inst.clear();
 +
        }
 +
      } catch (e) {
 +
        console.warn('[BAClickFX] pause error:', e);
 +
      }
 +
    });
 +
    hideAllFxCanvases();
 +
  }
 +
 
 +
  function resumeAll() {
 +
    showAllFxCanvases();
 +
    activeInstances.forEach(function (inst) {
 +
      try {
 +
        if (typeof inst.setPaused === 'function') {
 +
          inst.setPaused(false);
 +
        }
 +
      } catch (e) {
 +
        console.warn('[BAClickFX] resume error:', e);
 +
      }
 +
    });
 +
  }
 +
 
 +
  // 劫持 BAClickFX 类原型:拦截所有实例的事件分发与渲染
 +
  function patchBAClickFXClass(Cls) {
 +
    if (!Cls || !Cls.prototype || Cls.__rmzasPatched) return;
 +
    Cls.__rmzasPatched = true;
 +
    BAClickFXClass = Cls;
 +
 
 +
    var origPointerMove = Cls.prototype._handlePointerMove;
 +
    if (typeof origPointerMove === 'function') {
 +
      Cls.prototype._handlePointerMove = function (e) {
 +
        if (!shouldEnable()) return;
 +
        return origPointerMove.apply(this, arguments);
 +
      };
 +
    }
 +
 
 +
    var origPointerDown = Cls.prototype._handlePointerDown;
 +
    if (typeof origPointerDown === 'function') {
 +
      Cls.prototype._handlePointerDown = function (e) {
 +
        if (!shouldEnable()) return;
 +
        return origPointerDown.apply(this, arguments);
 +
      };
 +
    }
 +
 
 +
    var origRenderFrame = Cls.prototype._renderFrame;
 +
    if (typeof origRenderFrame === 'function') {
 +
      Cls.prototype._renderFrame = function (e) {
 +
        if (!shouldEnable()) {
 +
          this.animationFrame = null;
 +
          return;
 +
        }
 +
        return origRenderFrame.apply(this, arguments);
 +
      };
 +
    }
 +
 
 +
    var origMount = Cls.prototype._applyCompositingMount;
 +
    if (typeof origMount === 'function') {
 +
      Cls.prototype._applyCompositingMount = function () {
 +
        activeInstances.add(this);
 +
        var res = origMount.apply(this, arguments);
 +
        if (!shouldEnable()) {
 +
          try {
 +
            this.setPaused(true);
 +
            this.clear();
 +
          } catch (err) {}
 +
        }
 +
        return res;
 +
      };
 +
    }
 +
 
 +
    var origDestroy = Cls.prototype.destroy;
 +
    if (typeof origDestroy === 'function') {
 +
      Cls.prototype.destroy = function () {
 +
        activeInstances.delete(this);
 +
        return origDestroy.apply(this, arguments);
 +
      };
 +
    }
 +
 
 +
    if (!shouldEnable()) {
 +
      pauseAll();
 +
    }
 +
  }
 +
 
 +
  function ensureScriptLoadedAndPatched(callback) {
 +
    if (BAClickFXClass) {
 +
      if (callback) callback(BAClickFXClass);
 +
      return;
 +
    }
 +
    if (window.BAClickFX) {
 +
      patchBAClickFXClass(window.BAClickFX);
 +
      if (callback) callback(window.BAClickFX);
 +
      return;
 +
    }
 +
    if (isPatching) return;
 +
    isPatching = true;
 +
    try {
 +
      import(scriptUrl)
 +
        .then(function (module) {
 +
          isPatching = false;
 +
          var Cls = module.BAClickFX || module.default;
 +
          if (Cls) {
 +
            window.BAClickFX = Cls;
 +
            patchBAClickFXClass(Cls);
 +
            if (callback) callback(Cls);
 +
          }
 +
        })
 +
        .catch(function (error) {
 +
          isPatching = false;
 +
          console.error('[BAClickFX] 加载失败:', error);
 +
        });
 +
    } catch (error) {
 +
      isPatching = false;
 +
      console.error('[BAClickFX] 动态 import 不可用:', error);
 +
    }
 +
  }
 +
 
 +
  function applyFx() {
 +
    var enabled = shouldEnable();
 +
    root.setAttribute('data-rmzas-fx', mode);
 +
    root.classList.toggle('rmzas-fx-disabled', !enabled);
 +
    if (selector) selector.value = mode;
 +
 
 +
    if (!enabled) {
 +
      pauseAll();
 +
      return;
 +
    }
 +
 
 +
    resumeAll();
 +
 
 +
    // 如果还没有任何实例运行,则确保创建并挂载一个
 +
    if (activeInstances.size === 0) {
 +
      ensureScriptLoadedAndPatched(function (Cls) {
 +
        if (!shouldEnable() || activeInstances.size > 0) return;
 +
        try {
 +
          ensureTargetCanvas();
 +
          var instance = new Cls({
 +
            target: '#myCanvas',
 +
            trailAlways: true,
 +
            trailEnabled: true
 +
          });
 +
          activeInstances.add(instance);
 +
        } catch (error) {
 +
          console.error('[BAClickFX] 初始化失败:', error);
 +
        }
 +
      });
 +
    }
 +
  }
 +
 
 +
  try {
 +
    mode = normalize(window.localStorage.getItem(storageKey));
 +
  } catch (error) {
 +
    // 存储不可用时当前页面内保留选择。
 +
  }
 +
 
 +
  // 保证页面尽早有目标 Canvas
 +
  ensureTargetCanvas();
 +
 
 +
  // 启动劫持逻辑
 +
  ensureScriptLoadedAndPatched();
 +
 
 +
  // 尽早应用状态
 +
  applyFx();
 +
 
 +
  // 暴露全局接口
 +
  window.__rmzasGetFxState = shouldEnable;
 +
  window.__rmzasRegisterBAClickFX = function (instance) {
 +
    activeInstances.add(instance);
 +
    if (!shouldEnable()) {
 +
      pauseAll();
 +
    }
 +
  };
 +
 
 +
  window.addEventListener('storage', function (event) {
 +
    if (event.key === storageKey || event.key === null) {
 +
      mode = normalize(event.newValue);
 +
      applyFx();
 +
    }
 +
  });
 +
 
 +
  function addFxControl() {
 +
    if (document.getElementById('rmzas-fx-select')) return;
 +
 
 +
    var menu = document.querySelector('#p-personal ul, #p-personal .mw-portlet-body ul, #p-personal .body ul, #p-personal-inner ul');
 +
    var control = document.createElement(menu ? 'li' : 'div');
 +
    control.id = 'pt-rmzas-fx';
 +
    control.className = 'rmzas-fx-control';
 +
    if (!menu) control.classList.add('rmzas-fx-control-floating');
 +
 
 +
    var label = document.createElement('label');
 +
    label.htmlFor = 'rmzas-fx-select';
 +
    label.textContent = '鼠标特效';
 +
    selector = document.createElement('select');
 +
    selector.id = 'rmzas-fx-select';
 +
    selector.title = '选择是否开启鼠标光标与点击特效(此浏览器会记住你的选择)';
 +
 
 +
    [['on', '开启'], ['off', '关闭']].forEach(function (item) {
 +
      var option = document.createElement('option');
 +
      option.value = item[0];
 +
      option.textContent = item[1];
 +
      selector.appendChild(option);
 +
    });
 +
    selector.value = mode;
 +
    selector.addEventListener('change', function () {
 +
      mode = normalize(selector.value);
 +
      applyFx();
 +
      try {
 +
        window.localStorage.setItem(storageKey, mode);
 +
      } catch (error) {}
 +
    });
 +
 
 +
    control.appendChild(label);
 +
    control.appendChild(selector);
 +
 
 +
    var themeControl = document.getElementById('pt-rmzas-theme');
 +
    if (themeControl && themeControl.nextSibling) {
 +
      themeControl.parentNode.insertBefore(control, themeControl.nextSibling);
 +
    } else if (themeControl && themeControl.parentNode) {
 +
      themeControl.parentNode.appendChild(control);
 +
    } else if (menu) {
 +
      menu.appendChild(control);
 +
    } else {
 +
      document.body.appendChild(control);
 +
    }
 +
  }
 +
 
 +
  if (document.readyState === 'loading') {
 +
    document.addEventListener('DOMContentLoaded', addFxControl);
 +
  } else {
 +
    addFxControl();
 +
  }
 +
})();
 +
 
 +
// 显示最近更新的页面列表(带时间)
 
setTimeout(function() {
 
setTimeout(function() {
−
   console.log('开始加载最近更新列表(增强定位版)');
+
   //console.log('开始加载最近更新页面列表');
 
    
 
    
−
  // 1. 定位侧边栏
 
 
   var sidebar = document.getElementById('mw-panel');
 
   var sidebar = document.getElementById('mw-panel');
 
   if (!sidebar) {
 
   if (!sidebar) {
−
     console.log('未找到侧边栏(#mw-panel)');
+
     console.log('未找到侧边栏');
 
     return;
 
     return;
 
   }
 
   }
 
    
 
    
−
  // 2. 获取所有列表项并打印文本(方便核对)
 
 
   var items = sidebar.getElementsByTagName('li');
 
   var items = sidebar.getElementsByTagName('li');
−
  console.log('侧边栏列表项总数:', items.length);
 
−
  for (var i = 0; i < items.length; i++) {
 
−
    var itemText = items[i].textContent.trim(); // 去除前后空格
 
−
    console.log('第' + i + '个列表项文本:[' + itemText + ']'); // 用[]包裹,清晰显示空格
 
−
  }
 
−
 
 
−
  // 3. 灵活匹配容器(忽略标点和空格差异)
 
 
   var targetItem = null;
 
   var targetItem = null;
 
   for (var i = 0; i < items.length; i++) {
 
   for (var i = 0; i < items.length; i++) {
−
     var itemText = items[i].textContent.trim();
+
     if (items[i].textContent.indexOf('最近更新') !== -1) {
−
    // 匹配包含“最近更新”四个字的项(不管后面是什么标点/空格)
 
−
    if (itemText.indexOf('最近更新') !== -1) {
 
 
       targetItem = items[i];
 
       targetItem = items[i];
−
      console.log('找到目标容器,文本为:[' + itemText + ']');
 
 
       break;
 
       break;
 
     }
 
     }
第34行: 第419行:
 
    
 
    
 
   if (!targetItem) {
 
   if (!targetItem) {
−
     console.log('未找到包含“最近更新”的列表项,请检查侧边栏文本');
+
     console.log('未找到目标容器');
 
     return;
 
     return;
 
   }
 
   }
 
    
 
    
−
  // 后续逻辑(加载中、API请求、去重显示)保持不变
 
 
   targetItem.textContent = '';
 
   targetItem.textContent = '';
 
   targetItem.innerHTML = '最近更新:<br>加载中...';
 
   targetItem.innerHTML = '最近更新:<br>加载中...';
 
    
 
    
 +
  // API请求添加timestamp参数,获取更新时间
 
   var apiUrl = mw.util.wikiScript('api') + '?';
 
   var apiUrl = mw.util.wikiScript('api') + '?';
 
   apiUrl += 'action=query';
 
   apiUrl += 'action=query';
 
   apiUrl += '&list=recentchanges';
 
   apiUrl += '&list=recentchanges';
−
   apiUrl += '&rcprop=title';
+
   apiUrl += '&rcprop=title|timestamp'; // 同时获取标题和时间戳
 
   apiUrl += '&rclimit=10';
 
   apiUrl += '&rclimit=10';
 
   apiUrl += '&rcdaylimit=30';
 
   apiUrl += '&rcdaylimit=30';
第70行: 第455行:
 
         if (!seenTitles.has(title)) {
 
         if (!seenTitles.has(title)) {
 
           seenTitles.add(title);
 
           seenTitles.add(title);
−
           uniqueUpdates.push(page);
+
           uniqueUpdates.push(page); // 包含title和timestamp
−
           if (uniqueUpdates.length >= 5) break;
+
           if (uniqueUpdates.length >= 6) break;
 
         }
 
         }
 
       }
 
       }
第79行: 第464行:
 
       list.style.paddingLeft = '10px';
 
       list.style.paddingLeft = '10px';
 
       list.style.margin = '5px 0';
 
       list.style.margin = '5px 0';
−
     
 
−
      if (uniqueUpdates.length === 0) {
 
−
        targetItem.innerHTML += '暂无更新';
 
−
        return;
 
−
      }
 
 
        
 
        
 
       uniqueUpdates.forEach(function(page) {
 
       uniqueUpdates.forEach(function(page) {
 +
        // 处理时间戳:转换为本地时间(年-月-日 时:分)
 +
        var date = new Date(page.timestamp);
 +
        var timeStr = date.toLocaleString('zh-CN', {
 +
          month: '2-digit',    // 月份(2位)
 +
          day: '2-digit',      // 日期(2位)
 +
          hour: '2-digit',    // 小时(2位)
 +
          minute: '2-digit',  // 分钟(2位)
 +
          second: '2-digit',  // 新增:显示秒(2位)
 +
          hour12: false        // 强制24小时制(避免上午/下午)
 +
        }).replace(',', '');  // 移除可能的逗号分隔符
 +
       
 
         var listItem = document.createElement('li');
 
         var listItem = document.createElement('li');
 +
        listItem.style.margin = '3px 0'; // 每条间距
 +
       
 
         var link = document.createElement('a');
 
         var link = document.createElement('a');
 
         link.href = mw.util.getUrl(page.title);
 
         link.href = mw.util.getUrl(page.title);
 
         link.textContent = page.title;
 
         link.textContent = page.title;
−
         link.style.color = '#0645ad';
+
          
 +
        // 时间显示(灰色小字)
 +
        var timeSpan = document.createElement('span');
 +
        timeSpan.textContent = ' (' + timeStr + ')';
 +
        timeSpan.className = 'rmzas-recent-time';
 +
        timeSpan.style.fontSize = '0.8em'; // 小字
 +
        timeSpan.style.marginLeft = '5px';
 +
       
 
         listItem.appendChild(link);
 
         listItem.appendChild(link);
 +
        listItem.appendChild(timeSpan);
 
         list.appendChild(listItem);
 
         list.appendChild(listItem);
 
       });
 
       });
 
        
 
        
 
       targetItem.appendChild(list);
 
       targetItem.appendChild(list);
−
      console.log('去重后显示 ' + uniqueUpdates.length + ' 个页面');
 
 
     })
 
     })
 
     .catch(function(error) {
 
     .catch(function(error) {
第103行: 第503行:
 
     });
 
     });
 
    
 
    
−
}, 1000); // 延长延迟到1秒,确保DOM完全加载
+
}, 1000);

2026年10月22日 (四) 02:33:24的最新版本

/* 这里的任何JavaScript将为所有用户在每次页面载入时加载。 */

/* 阅读主题:仅在当前浏览器保存,未选择时跟随系统。 */
(function () {
  'use strict';

  var root = document.documentElement;
  var storageKey = 'rmzas-wiki-theme';
  var systemTheme = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
  var selector;
  var mode = 'auto';

  // 防止脚本重复执行时添加多个控件或事件监听。
  if (root.hasAttribute('data-rmzas-theme')) return;

  function normalize(value) {
    return value === 'light' || value === 'dark' ? value : 'auto';
  }

  function applyTheme() {
    var dark = mode === 'dark' || (mode === 'auto' && systemTheme && systemTheme.matches);
    root.setAttribute('data-rmzas-theme', mode);
    root.classList.toggle('rmzas-theme-dark', !!dark);
    if (selector) selector.value = mode;
  }

  try {
    mode = normalize(window.localStorage.getItem(storageKey));
  } catch (error) {
    // 存储不可用时仍允许切换,当前页面内保留选择。
  }
  // 不等待 DOM 就绪,尽早应用配色;Common.js 本身仍是异步加载的。
  applyTheme();

  if (systemTheme) {
    if (systemTheme.addEventListener) {
      systemTheme.addEventListener('change', applyTheme);
    } else if (systemTheme.addListener) {
      systemTheme.addListener(applyTheme);
    }
  }

  window.addEventListener('storage', function (event) {
    if (event.key === storageKey || event.key === null) {
      mode = normalize(event.newValue);
      applyTheme();
    }
  });

  function addThemeControl() {
    if (document.getElementById('rmzas-theme-select')) return;

    // Vector / MonoBook 的个人工具栏;Timeless 使用用户链接区域。
    var menu = document.querySelector('#p-personal ul, #p-personal .mw-portlet-body ul, #p-personal .body ul, #p-personal-inner ul');
    var control = document.createElement(menu ? 'li' : 'div');
    control.id = 'pt-rmzas-theme';
    control.className = 'rmzas-theme-control';
    if (!menu) control.classList.add('rmzas-theme-control-floating');

    var label = document.createElement('label');
    label.htmlFor = 'rmzas-theme-select';
    label.textContent = '主题';
    selector = document.createElement('select');
    selector.id = 'rmzas-theme-select';
    selector.title = '选择阅读主题(此浏览器会记住你的选择)';

    [['auto', '跟随系统'], ['light', '浅色'], ['dark', '深色']].forEach(function (item) {
      var option = document.createElement('option');
      option.value = item[0];
      option.textContent = item[1];
      selector.appendChild(option);
    });
    selector.value = mode;
    selector.addEventListener('change', function () {
      mode = normalize(selector.value);
      applyTheme();
      try {
        window.localStorage.setItem(storageKey, mode);
      } catch (error) {
        // 隐私设置禁用存储时不影响配色或原有站点脚本。
      }
    });

    control.appendChild(label);
    control.appendChild(selector);
    if (menu) {
      menu.insertBefore(control, menu.firstChild);
    } else {
      document.body.appendChild(control);
    }
  }

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

/* BA鼠标光标与点击特效:支持开启/关闭,仅在当前浏览器保存。 */
(function () {
  'use strict';

  var root = document.documentElement;
  var storageKey = 'rmzas-wiki-fx';
  var scriptUrl = 'http://rmzas.site/wp-content/themes/twentyfifteen/js/ba-click-fx/ba-click-fx.js';
  var motionQuery = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
  var BAClickFXClass = null;
  var activeInstances = new Set();
  var isPatching = false;
  var selector;
  var mode = 'on'; // 'on' | 'off'

  if (root.hasAttribute('data-rmzas-fx')) return;

  function normalize(value) {
    if (value === 'off') return 'off';
    if (value === 'on') return 'on';
    // 首次访问未设置时:若操作系统开启了减弱动态效果则默认关闭,否则默认开启
    return (motionQuery && motionQuery.matches) ? 'off' : 'on';
  }

  function shouldEnable() {
    return mode === 'on';
  }

  // 确保页面中有 #myCanvas 供 BAClickFX 挂载,防止其回退生成独立的顶层 canvas
  function ensureTargetCanvas() {
    var cvs = document.getElementById('myCanvas');
    if (!cvs) {
      cvs = document.createElement('canvas');
      cvs.id = 'myCanvas';
      if (document.body) {
        document.body.appendChild(cvs);
      } else {
        document.addEventListener('DOMContentLoaded', function () {
          if (!document.getElementById('myCanvas') && document.body) {
            document.body.appendChild(cvs);
          }
        });
      }
    }
    return cvs;
  }

  function hideAllFxCanvases() {
    var canvases = document.querySelectorAll('canvas');
    for (var i = 0; i < canvases.length; i++) {
      canvases[i].style.setProperty('display', 'none', 'important');
      canvases[i].style.setProperty('visibility', 'hidden', 'important');
    }
  }

  function showAllFxCanvases() {
    var canvases = document.querySelectorAll('canvas');
    for (var i = 0; i < canvases.length; i++) {
      canvases[i].style.removeProperty('display');
      canvases[i].style.removeProperty('visibility');
    }
  }

  function pauseAll() {
    activeInstances.forEach(function (inst) {
      try {
        if (typeof inst.setPaused === 'function') {
          inst.setPaused(true);
        }
        if (typeof inst.clear === 'function') {
          inst.clear();
        }
      } catch (e) {
        console.warn('[BAClickFX] pause error:', e);
      }
    });
    hideAllFxCanvases();
  }

  function resumeAll() {
    showAllFxCanvases();
    activeInstances.forEach(function (inst) {
      try {
        if (typeof inst.setPaused === 'function') {
          inst.setPaused(false);
        }
      } catch (e) {
        console.warn('[BAClickFX] resume error:', e);
      }
    });
  }

  // 劫持 BAClickFX 类原型:拦截所有实例的事件分发与渲染
  function patchBAClickFXClass(Cls) {
    if (!Cls || !Cls.prototype || Cls.__rmzasPatched) return;
    Cls.__rmzasPatched = true;
    BAClickFXClass = Cls;

    var origPointerMove = Cls.prototype._handlePointerMove;
    if (typeof origPointerMove === 'function') {
      Cls.prototype._handlePointerMove = function (e) {
        if (!shouldEnable()) return;
        return origPointerMove.apply(this, arguments);
      };
    }

    var origPointerDown = Cls.prototype._handlePointerDown;
    if (typeof origPointerDown === 'function') {
      Cls.prototype._handlePointerDown = function (e) {
        if (!shouldEnable()) return;
        return origPointerDown.apply(this, arguments);
      };
    }

    var origRenderFrame = Cls.prototype._renderFrame;
    if (typeof origRenderFrame === 'function') {
      Cls.prototype._renderFrame = function (e) {
        if (!shouldEnable()) {
          this.animationFrame = null;
          return;
        }
        return origRenderFrame.apply(this, arguments);
      };
    }

    var origMount = Cls.prototype._applyCompositingMount;
    if (typeof origMount === 'function') {
      Cls.prototype._applyCompositingMount = function () {
        activeInstances.add(this);
        var res = origMount.apply(this, arguments);
        if (!shouldEnable()) {
          try {
            this.setPaused(true);
            this.clear();
          } catch (err) {}
        }
        return res;
      };
    }

    var origDestroy = Cls.prototype.destroy;
    if (typeof origDestroy === 'function') {
      Cls.prototype.destroy = function () {
        activeInstances.delete(this);
        return origDestroy.apply(this, arguments);
      };
    }

    if (!shouldEnable()) {
      pauseAll();
    }
  }

  function ensureScriptLoadedAndPatched(callback) {
    if (BAClickFXClass) {
      if (callback) callback(BAClickFXClass);
      return;
    }
    if (window.BAClickFX) {
      patchBAClickFXClass(window.BAClickFX);
      if (callback) callback(window.BAClickFX);
      return;
    }
    if (isPatching) return;
    isPatching = true;
    try {
      import(scriptUrl)
        .then(function (module) {
          isPatching = false;
          var Cls = module.BAClickFX || module.default;
          if (Cls) {
            window.BAClickFX = Cls;
            patchBAClickFXClass(Cls);
            if (callback) callback(Cls);
          }
        })
        .catch(function (error) {
          isPatching = false;
          console.error('[BAClickFX] 加载失败:', error);
        });
    } catch (error) {
      isPatching = false;
      console.error('[BAClickFX] 动态 import 不可用:', error);
    }
  }

  function applyFx() {
    var enabled = shouldEnable();
    root.setAttribute('data-rmzas-fx', mode);
    root.classList.toggle('rmzas-fx-disabled', !enabled);
    if (selector) selector.value = mode;

    if (!enabled) {
      pauseAll();
      return;
    }

    resumeAll();

    // 如果还没有任何实例运行,则确保创建并挂载一个
    if (activeInstances.size === 0) {
      ensureScriptLoadedAndPatched(function (Cls) {
        if (!shouldEnable() || activeInstances.size > 0) return;
        try {
          ensureTargetCanvas();
          var instance = new Cls({
            target: '#myCanvas',
            trailAlways: true,
            trailEnabled: true
          });
          activeInstances.add(instance);
        } catch (error) {
          console.error('[BAClickFX] 初始化失败:', error);
        }
      });
    }
  }

  try {
    mode = normalize(window.localStorage.getItem(storageKey));
  } catch (error) {
    // 存储不可用时当前页面内保留选择。
  }

  // 保证页面尽早有目标 Canvas
  ensureTargetCanvas();

  // 启动劫持逻辑
  ensureScriptLoadedAndPatched();

  // 尽早应用状态
  applyFx();

  // 暴露全局接口
  window.__rmzasGetFxState = shouldEnable;
  window.__rmzasRegisterBAClickFX = function (instance) {
    activeInstances.add(instance);
    if (!shouldEnable()) {
      pauseAll();
    }
  };

  window.addEventListener('storage', function (event) {
    if (event.key === storageKey || event.key === null) {
      mode = normalize(event.newValue);
      applyFx();
    }
  });

  function addFxControl() {
    if (document.getElementById('rmzas-fx-select')) return;

    var menu = document.querySelector('#p-personal ul, #p-personal .mw-portlet-body ul, #p-personal .body ul, #p-personal-inner ul');
    var control = document.createElement(menu ? 'li' : 'div');
    control.id = 'pt-rmzas-fx';
    control.className = 'rmzas-fx-control';
    if (!menu) control.classList.add('rmzas-fx-control-floating');

    var label = document.createElement('label');
    label.htmlFor = 'rmzas-fx-select';
    label.textContent = '鼠标特效';
    selector = document.createElement('select');
    selector.id = 'rmzas-fx-select';
    selector.title = '选择是否开启鼠标光标与点击特效(此浏览器会记住你的选择)';

    [['on', '开启'], ['off', '关闭']].forEach(function (item) {
      var option = document.createElement('option');
      option.value = item[0];
      option.textContent = item[1];
      selector.appendChild(option);
    });
    selector.value = mode;
    selector.addEventListener('change', function () {
      mode = normalize(selector.value);
      applyFx();
      try {
        window.localStorage.setItem(storageKey, mode);
      } catch (error) {}
    });

    control.appendChild(label);
    control.appendChild(selector);

    var themeControl = document.getElementById('pt-rmzas-theme');
    if (themeControl && themeControl.nextSibling) {
      themeControl.parentNode.insertBefore(control, themeControl.nextSibling);
    } else if (themeControl && themeControl.parentNode) {
      themeControl.parentNode.appendChild(control);
    } else if (menu) {
      menu.appendChild(control);
    } else {
      document.body.appendChild(control);
    }
  }

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

// 显示最近更新的页面列表(带时间)
setTimeout(function() {
  //console.log('开始加载最近更新页面列表');
  
  var sidebar = document.getElementById('mw-panel');
  if (!sidebar) {
    console.log('未找到侧边栏');
    return;
  }
  
  var items = sidebar.getElementsByTagName('li');
  var targetItem = null;
  for (var i = 0; i < items.length; i++) {
    if (items[i].textContent.indexOf('最近更新') !== -1) {
      targetItem = items[i];
      break;
    }
  }
  
  if (!targetItem) {
    console.log('未找到目标容器');
    return;
  }
  
  targetItem.textContent = '';
  targetItem.innerHTML = '最近更新:<br>加载中...';
  
  // API请求添加timestamp参数,获取更新时间
  var apiUrl = mw.util.wikiScript('api') + '?';
  apiUrl += 'action=query';
  apiUrl += '&list=recentchanges';
  apiUrl += '&rcprop=title|timestamp'; // 同时获取标题和时间戳
  apiUrl += '&rclimit=10';
  apiUrl += '&rcdaylimit=30';
  apiUrl += '&format=json';
  
  fetch(apiUrl)
    .then(function(response) {
      if (!response.ok) throw new Error('API请求失败:' + response.status);
      return response.json();
    })
    .then(function(data) {
      targetItem.innerHTML = '最近更新:<br>';
      
      if (!data.query || !data.query.recentchanges || data.query.recentchanges.length === 0) {
        targetItem.innerHTML += '暂无更新';
        return;
      }
      
      var seenTitles = new Set();
      var uniqueUpdates = [];
      for (var i = 0; i < data.query.recentchanges.length; i++) {
        var page = data.query.recentchanges[i];
        var title = page.title;
        if (!seenTitles.has(title)) {
          seenTitles.add(title);
          uniqueUpdates.push(page); // 包含title和timestamp
          if (uniqueUpdates.length >= 6) break;
        }
      }
      
      var list = document.createElement('ul');
      list.style.listStyle = 'none';
      list.style.paddingLeft = '10px';
      list.style.margin = '5px 0';
      
      uniqueUpdates.forEach(function(page) {
        // 处理时间戳:转换为本地时间(年-月-日 时:分)
        var date = new Date(page.timestamp);
        var timeStr = date.toLocaleString('zh-CN', {
          month: '2-digit',    // 月份(2位)
          day: '2-digit',      // 日期(2位)
          hour: '2-digit',     // 小时(2位)
          minute: '2-digit',   // 分钟(2位)
          second: '2-digit',   // 新增:显示秒(2位)
          hour12: false        // 强制24小时制(避免上午/下午)
        }).replace(',', '');   // 移除可能的逗号分隔符
        
        var listItem = document.createElement('li');
        listItem.style.margin = '3px 0'; // 每条间距
        
        var link = document.createElement('a');
        link.href = mw.util.getUrl(page.title);
        link.textContent = page.title;
        
        // 时间显示(灰色小字)
        var timeSpan = document.createElement('span');
        timeSpan.textContent = ' (' + timeStr + ')';
        timeSpan.className = 'rmzas-recent-time';
        timeSpan.style.fontSize = '0.8em'; // 小字
        timeSpan.style.marginLeft = '5px';
        
        listItem.appendChild(link);
        listItem.appendChild(timeSpan);
        list.appendChild(listItem);
      });
      
      targetItem.appendChild(list);
    })
    .catch(function(error) {
      console.error('加载失败:', error);
      targetItem.innerHTML = '最近更新:<br>加载失败';
    });
  
}, 1000);