打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

MediaWiki:Gadget-site-js.js

MediaWiki界面页面
棍牧典留言 | 贡献2025年6月21日 (六) 08:02的版本

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

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
/********************** 
 * 模块 0:优先加载 vConsole(立即执行)
 **********************/
(function() {
  // 调试控制开关(生产环境可设置为 false)
  const DEBUG_MODE = true;
  
  if (DEBUG_MODE && !window.localStorage.getItem('disableVConsole')) {
    console.log('[Init] 正在加载调试控制台...');
    
    const vConsoleScript = document.createElement('script');
    vConsoleScript.src = 'https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js';
    vConsoleScript.integrity = 'sha384-+EmbrakM5WQd6BSpR2GQKJqH15zFVECXZQ5qPj4hEtZj0jFz4iF2PnmFUTTvWH+1';
    vConsoleScript.crossOrigin = 'anonymous';
    
    vConsoleScript.onload = function() {
      try {
        // 初始化 vConsole 并配置
        const vConsole = new window.VConsole({
          theme: 'dark',
          onReady: function() {
            console.log('[vConsole] 调试面板已就绪');
            // 捕获未处理的Promise错误
            window.addEventListener('unhandledrejection', event => {
              console.error('[未处理的Promise错误]', event.reason);
            });
          }
        });
        
        // 全局错误监听(捕获语法错误之外的运行时错误)
        window.addEventListener('error', function(event) {
          console.error(
            '[全局错误]',
            `类型: ${event.error?.name || 'Error'}\n`,
            `信息: ${event.message}\n`,
            `文件: ${event.filename}\n`,
            `行号: ${event.lineno}:${event.colno}`
          );
        });
        
      } catch (e) {
        console.error('[vConsole] 初始化失败', e);
      }
    };
    
    vConsoleScript.onerror = function() {
      console.warn('[vConsole] 加载失败,尝试备用CDN...');
      loadFallbackVConsole();
    };
    
    document.head.insertBefore(vConsoleScript, document.head.firstChild);
    
    // 备用CDN加载
    function loadFallbackVConsole() {
      const fallbackScript = document.createElement('script');
      fallbackScript.src = 'https://cdn.jsdelivr.net/npm/vconsole@3.15.1/dist/vconsole.min.js';
      fallbackScript.onload = vConsoleScript.onload;
      fallbackScript.onerror = function() {
        console.error('[vConsole] 所有CDN加载失败,将无法显示控制台');
      };
      document.head.appendChild(fallbackScript);
    }
  }
})();

/**********************
 * 模块 1:CSS 变量 RGB 转换(安全封装)
 **********************/
;(function() {
  try {
    const ColorUtil = (function () {
      const OBS_VAR = '--background-color-base';
      let cachedRgb = '';

      function parseHex(hex) {
        try {
          hex = hex.replace(/^#/, '');
          if (hex.length === 3) {
            hex = hex.split('').map(c => c + c).join('');
          }
          if (hex.length !== 6) return '0, 0, 0';
          const [r, g, b] = [0, 2, 4].map(i => parseInt(hex.substring(i, i + 2), 16) || 0);
          return `${r}, ${g}, ${b}`;
        } catch (e) {
          console.error('[ColorUtil] 颜色解析失败', e);
          return '0, 0, 0';
        }
      }

      function safeUpdate() {
        try {
          const root = document.documentElement;
          const hex = getComputedStyle(root).getPropertyValue(OBS_VAR).trim();
          const rgb = parseHex(hex || '#000');
          if (rgb !== cachedRgb) {
            root.style.setProperty('--background-color-rgb', rgb);
            cachedRgb = rgb;
            console.log('[ColorUtil] RGB值已更新:', rgb);
          }
        } catch (e) {
          console.warn('[ColorUtil] 更新失败', e);
        }
      }

      function init() {
        try {
          safeUpdate();
          const mo = new MutationObserver(safeUpdate);
          mo.observe(document.documentElement, { 
            attributes: true, 
            attributeFilter: ['style'] 
          });
          return {
            destroy: () => mo.disconnect(),
            forceUpdate: safeUpdate
          };
        } catch (e) {
          console.error('[ColorUtil] 初始化失败', e);
          return { destroy: () => {}, forceUpdate: () => {} };
        }
      }

      return init();
    })();
    
    // 暴露到全局(可选)
    window.ColorUtil = ColorUtil;
    
  } catch (e) {
    console.error('[模块1] 初始化异常', e);
  }
})();

/**********************
 * 模块 2:上传页引导弹窗(安全封装)
 **********************/
;(function() {
  try {
    if (!window.mw || !mw.config || mw.config.get('wgCanonicalSpecialPageName') !== 'Upload') {
      return;
    }

    const KEY = 'uploadPreference_v2';
    const pref = mw.storage.get(KEY);

    if (pref === 'wizard') {
      window.location.href = mw.util.getUrl('Special:UploadWizard');
      return;
    }
    if (pref === 'classic') return;

    function initDialog(event) {
      // 确保只触发一次
      if (event && event.target.closest('#mw-content-text')) {
        document.removeEventListener('click', initDialog);
        showDialog();
      }
    }

    function showDialog() {
      mw.loader.using(['mediawiki.util', 'mediawiki.storage', 'oojs-ui-core', 'oojs-ui-widgets'])
        .then(() => {
          try {
            class UploadDialog extends OO.ui.ProcessDialog {
              static static = {
                name: 'UploadDialog',
                title: '请选择上传方式',
                actions: [
                  { 
                    action: 'classic',
                    label: '❌ 使用传统上传方式',
                    flags: ['safe']
                  }
                ]
              };

              initialize() {
                super.initialize();
                const panel = new OO.ui.PanelLayout({ padded: true });
                this._remember = new OO.ui.CheckboxInputWidget();
                const rememberField = new OO.ui.FieldLayout(this._remember, {
                  label: '记住我的选择',
                  align: 'inline'
                });

                const wizardBtn = new OO.ui.ButtonWidget({
                  label: '✅ 使用上传向导(推荐)',
                  flags: ['primary', 'progressive'],
                  href: mw.util.getUrl('Special:UploadWizard'),
                  target: '_self'
                });

                wizardBtn.on('click', () => {
                  if (this._remember.isSelected()) {
                    mw.storage.set(KEY, 'wizard');
                  }
                });

                panel.$element.append(
                  $('<p>').text('请选择上传方式:'),
                  $('<div>').css('margin', '10px 0').append(wizardBtn.$element),
                  $('<div>').append(rememberField.$element)
                );

                this.$body.append(panel.$element);
              }

              getActionProcess(action) {
                if (action === 'classic') {
                  return new OO.ui.Process(() => {
                    if (this._remember.isSelected()) {
                      mw.storage.set(KEY, 'classic');
                    }
                    this.close();
                  });
                }
                return super.getActionProcess(action);
              }
            }

            const windowManager = new OO.ui.WindowManager();
            document.body.appendChild(windowManager.$element);
            windowManager.addWindows([new UploadDialog()]);
            windowManager.openWindow('UploadDialog');

          } catch (e) {
            console.error('[UploadDialog] 创建失败', e);
            // 降级处理:直接跳转
            window.location.href = mw.util.getUrl('Special:UploadWizard');
          }
        })
        .catch(e => {
          console.error('[OOUI] 加载失败', e);
        });
    }

    // 延迟绑定事件,避免阻塞
    setTimeout(() => {
      document.addEventListener('click', initDialog, { once: true });
    }, 500);

  } catch (e) {
    console.error('[模块2] 初始化异常', e);
  }
})();

/**********************
 * 模块 3:全局加载指示器(安全封装)
 **********************/
;(function() {
  try {
    const LoadingIndicator = (function () {
      // 配置项(可自定义)
      const config = {
        imageUrl: 'https://example.com/loading-spinner.svg',
        fallbackImage: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0MCIgc3Ryb2tlPSIjMDA3M2JhIiBzdHJva2Utd2lkdGg9IjgiIGZpbGw9Im5vbmUiIHN0cm9rZS1kYXNoYXJyYXk9IjYyLjgzIDYyLjgzIiB0cmFuc2Zvcm09InJvdGF0ZSgzNjAgNTAgNTApIj48YW5pbWF0ZVRyYW5zZm9ybSBhdHRyaWJ1dGVOYW1lPSJ0cmFuc2Zvcm0iIHR5cGU9InJvdGF0ZSIgcmVwZWF0Q291bnQ9ImluZGVmaW5pdGUiIGR1cj0iMXMiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiLz48L2NpcmNsZT48L3N2Zz4=',
        size: '64px',
        position: { bottom: '20px', right: '20px' },
        timeout: 15000,
        animation: { fadeIn: 300, fadeOut: 500 },
        zIndex: 99999
      };

      let instance = null;
      let timeoutId = null;
      let startTime = 0;

      function createLoader() {
        const loader = document.createElement('div');
        loader.className = 'global-loader';
        loader.setAttribute('aria-live', 'polite');
        
        Object.assign(loader.style, {
          position: 'fixed',
          width: config.size,
          height: config.size,
          bottom: config.position.bottom,
          right: config.position.right,
          opacity: '0',
          transition: `opacity ${config.animation.fadeIn}ms ease-out`,
          zIndex: config.zIndex,
          pointerEvents: 'none',
          backgroundImage: `url(${config.imageUrl})`,
          backgroundSize: 'contain',
          backgroundRepeat: 'no-repeat'
        });

        // 备用图片处理
        loader.onerror = function() {
          this.style.backgroundImage = `url(${config.fallbackImage})`;
        };

        return loader;
      }

      function show() {
        if (instance) return resetTimer();
        
        startTime = Date.now();
        instance = createLoader();
        document.body.appendChild(instance);
        
        requestAnimationFrame(() => {
          instance.style.opacity = '1';
        });
        
        resetTimer();
        addEventListeners();
      }

      function hide() {
        if (!instance) return;
        
        const elapsed = Date.now() - startTime;
        const minDisplayTime = 500; // 最少显示500ms
        const delay = Math.max(minDisplayTime - elapsed, 0);
        
        clearTimeout(timeoutId);
        
        setTimeout(() => {
          instance.style.opacity = '0';
          instance.setAttribute('aria-busy', 'false');
          
          setTimeout(() => {
            if (instance && instance.parentNode) {
              instance.parentNode.removeChild(instance);
            }
            instance = null;
            removeEventListeners();
          }, config.animation.fadeOut);
        }, delay);
      }

      function resetTimer() {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(hide, config.timeout);
      }

      function handleOnline() {
        console.log('[Loader] 网络恢复');
      }

      function handleOffline() {
        console.warn('[Loader] 网络断开');
        hide();
      }

      function addEventListeners() {
        window.addEventListener('online', handleOnline);
        window.addEventListener('offline', handleOffline);
      }

      function removeEventListeners() {
        window.removeEventListener('online', handleOnline);
        window.removeEventListener('offline', handleOffline);
      }

      // 自动绑定生命周期事件
      document.addEventListener('DOMContentLoaded', show);
      window.addEventListener('load', hide);
      
      // 页面离开时清理
      window.addEventListener('beforeunload', () => {
        if (instance) {
          hide();
        }
      });

      return {
        show,
        hide,
        updateConfig: (newConfig) => {
          Object.assign(config, newConfig);
        }
      };
    })();

    // 暴露到全局(可选)
    window.LoadingIndicator = LoadingIndicator;

  } catch (e) {
    console.error('[模块3] 初始化异常', e);
  }
})();

/**********************
 * 错误兜底处理(最后执行)
 **********************/
;(function() {
  // 如果vConsole未加载,显示简化错误提示
  if (!window.vConsole) {
    window.addEventListener('error', function(event) {
      console.error('页面错误:', event.error);
      const errorBox = document.createElement('div');
      errorBox.style.cssText = `
        position: fixed;
        bottom: 20px;
        left: 20px;
        right: 20px;
        padding: 15px;
        background: #ffebee;
        border: 2px solid #f44336;
        border-radius: 5px;
        z-index: 99999;
        font-family: sans-serif;
      `;
      errorBox.innerHTML = `
        <h3 style="margin-top:0;color:#d32f2f">页面错误</h3>
        <p>${event.message}</p>
        <button onclick="this.parentNode.remove()" 
          style="background:#f44336;color:white;border:none;padding:5px 10px;border-radius:3px">
          关闭
        </button>
      `;
      document.body.appendChild(errorBox);
    });
  }
})();