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

MediaWiki:Gadget-site-js.js:修订间差异

MediaWiki界面页面
第1行: 第1行:
/**********************  
/**********************  
  * 模块 0:优先加载 vConsole(立即执行)
  * 模块 0:vConsole 加载
  **********************/
  **********************/
(function() {
const vConsoleScript = document.createElement('script');
  // 调试控制开关(生产环境可设置为 false)
vConsoleScript.src = 'https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js';
  const DEBUG_MODE = true;
document.head.appendChild(vConsoleScript);
 
vConsoleScript.onload = () => new VConsole();
  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 转换(安全封装)
  * 模块 1:CSS 变量 RGB 转换
  **********************/
  **********************/
;(function() {
const ColorUtil = (function () {
  try {
  const OBS_VAR = '--background-color-base';
    const ColorUtil = (function () {
  let cachedRgb = '';
      const OBS_VAR = '--background-color-base';
      let cachedRgb = '';


      function parseHex(hex) {
  function parseHex(hex) {
        try {
    hex = hex.replace(/^#/, '');
          hex = hex.replace(/^#/, '');
    if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
          if (hex.length === 3) {
    if (hex.length !== 6) return '0, 0, 0';
            hex = hex.split('').map(c => c + c).join('');
    const [r, g, b] = [0, 2, 4].map(i => parseInt(hex.substring(i, i + 2), 16));
          }
    return `${r}, ${g}, ${b}`;
          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() {
  function update() {
        try {
    const root = document.documentElement;
          const root = document.documentElement;
    const hex = getComputedStyle(root).getPropertyValue(OBS_VAR).trim();
          const hex = getComputedStyle(root).getPropertyValue(OBS_VAR).trim();
    const rgb = parseHex(hex || '#000');
          const rgb = parseHex(hex || '#000');
    if (rgb !== cachedRgb) {
          if (rgb !== cachedRgb) {
      root.style.setProperty('--background-color-rgb', rgb);
            root.style.setProperty('--background-color-rgb', rgb);
      cachedRgb = rgb;
            cachedRgb = rgb;
    }
            console.log('[ColorUtil] RGB值已更新:', rgb);
  }
          }
        } catch (e) {
          console.warn('[ColorUtil] 更新失败', e);
        }
      }


      function init() {
  update();
        try {
  const mo = new MutationObserver(update);
          safeUpdate();
  mo.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });
          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();
  return {
     })();
     destroy() { mo.disconnect(); }
   
   };
    // 暴露到全局(可选)
    window.ColorUtil = ColorUtil;
   
  } catch (e) {
    console.error('[模块1] 初始化异常', e);
   }
})();
})();


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


    const KEY = 'uploadPreference_v2';
  if (pref === 'wizard') window.location = mw.util.getUrl('Special:UploadWizard');
    const pref = mw.storage.get(KEY);
  if (pref === 'classic') ;


    if (pref === 'wizard') {
  document.addEventListener('click', function initDialog() {
      window.location.href = mw.util.getUrl('Special:UploadWizard');
    document.removeEventListener('click', initDialog);
       return;
    mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets']).then(() => {
    }
       class UploadDialog extends OO.ui.ProcessDialog {
    if (pref === 'classic') return;
        static name = 'UploadDialog';
        static title = '请选择上传方式';
        static actions = [{ action: 'classic', label: '❌ 传统方式', flags: ['safe'] }];


    function initDialog(event) {
         initialize() {
      // 确保只触发一次
           super.initialize();
      if (event && event.target.closest('#mw-content-text')) {
          this._remember = new OO.ui.CheckboxInputWidget();
         document.removeEventListener('click', initDialog);
          const wizardBtn = new OO.ui.ButtonWidget({
        showDialog();
            label: '✅ 上传向导',
      }
            flags: ['primary'],
    }
            href: mw.util.getUrl('Special:UploadWizard'),
 
            target: '_self'
    function showDialog() {
          });
      mw.loader.using(['mediawiki.util', 'mediawiki.storage', 'oojs-ui-core', 'oojs-ui-widgets'])
          wizardBtn.on('click', () => {
        .then(() => {
            if (this._remember.isSelected()) mw.storage.set(KEY, 'wizard');
           try {
          });
            class UploadDialog extends OO.ui.ProcessDialog {
          this.$body.append(
              static static = {
            $('<p>').text('请选择上传方式:'),
                name: 'UploadDialog',
            wizardBtn.$element,
                title: '请选择上传方式',
            new OO.ui.FieldLayout(this._remember, { label: '记住选择', align: 'inline' }).$element
                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') {
 
            if (this._remember.isSelected()) mw.storage.set(KEY, 'classic');
              getActionProcess(action) {
            return new OO.ui.Process(() => this.close());
                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');
           }
           }
        })
           return super.getActionProcess(action);
        .catch(e => {
         }
           console.error('[OOUI] 加载失败', e);
      }
         });
    }


    // 延迟绑定事件,避免阻塞
      const wm = new OO.ui.WindowManager();
    setTimeout(() => {
       document.body.appendChild(wm.$element);
       document.addEventListener('click', initDialog, { once: true });
      wm.addWindows([new UploadDialog()]);
    }, 500);
      wm.openWindow('UploadDialog');
 
    });
  } catch (e) {
  });
    console.error('[模块2] 初始化异常', e);
}
  }
})();


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


      let instance = null;
  let indicator = null;
      let timeoutId = null;
  let timerId = null;
      let startTime = 0;
  let currentConfig = { ...defaultConfig };


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


        // 备用图片处理
  function show() {
        loader.onerror = function() {
    if (indicator) return;
          this.style.backgroundImage = `url(${config.fallbackImage})`;
   
        };
    indicator = createIndicator();
    document.body.appendChild(indicator);
   
    setTimeout(() => {
      indicator.style.opacity = '1';
    }, 10);
   
    timerId = setTimeout(hide, currentConfig.timeout);
  }


        return loader;
  function hide() {
       }
    if (!indicator) return;
   
    indicator.style.opacity = '0';
    setTimeout(() => {
      indicator.remove();
       indicator = null;
    }, currentConfig.fadeDuration);
   
    clearTimeout(timerId);
  }


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


      function hide() {
  document.addEventListener('DOMContentLoaded', show);
        if (!instance) return;
  window.addEventListener('load', hide);
       
        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() {
  return {
        clearTimeout(timeoutId);
    show,
        timeoutId = setTimeout(hide, config.timeout);
    hide,
      }
    updateConfig
 
   };
      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);
    });
  }
})();
})();

2025年6月21日 (六) 08:10的版本

/********************** 
 * 模块 0:vConsole 加载
 **********************/
const vConsoleScript = document.createElement('script');
vConsoleScript.src = 'https://cdn.bootcdn.net/ajax/libs/vConsole/3.15.1/vconsole.min.js';
document.head.appendChild(vConsoleScript);
vConsoleScript.onload = () => new VConsole();

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

  function parseHex(hex) {
    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));
    return `${r}, ${g}, ${b}`;
  }

  function update() {
    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;
    }
  }

  update();
  const mo = new MutationObserver(update);
  mo.observe(document.documentElement, { attributes: true, attributeFilter: ['style'] });

  return {
    destroy() { mo.disconnect(); }
  };
})();

/**********************
 * 模块 2:上传页引导弹窗
 **********************/
if (mw?.config?.get('wgCanonicalSpecialPageName') === 'Upload') {
  const KEY = 'uploadPreference';
  const pref = mw.storage.get(KEY);

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

  document.addEventListener('click', function initDialog() {
    document.removeEventListener('click', initDialog);
    mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets']).then(() => {
      class UploadDialog extends OO.ui.ProcessDialog {
        static name = 'UploadDialog';
        static title = '请选择上传方式';
        static actions = [{ action: 'classic', label: '❌ 传统方式', flags: ['safe'] }];

        initialize() {
          super.initialize();
          this._remember = new OO.ui.CheckboxInputWidget();
          const wizardBtn = new OO.ui.ButtonWidget({
            label: '✅ 上传向导',
            flags: ['primary'],
            href: mw.util.getUrl('Special:UploadWizard'),
            target: '_self'
          });
          wizardBtn.on('click', () => {
            if (this._remember.isSelected()) mw.storage.set(KEY, 'wizard');
          });
          this.$body.append(
            $('<p>').text('请选择上传方式:'),
            wizardBtn.$element,
            new OO.ui.FieldLayout(this._remember, { label: '记住选择', align: 'inline' }).$element
          );
        }

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

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

/**********************
 * 模块 3:加载指示器(带配置)
 **********************/
const LoadingIndicator = (function () {
  const defaultConfig = {
    imageUrl: 'https://wiki.ottohub.cn/images/0/02/Loading.png',
    position: { bottom: '20px', right: '20px' },
    size: '256px',
    timeout: 15000,
    zIndex: '99999',
    fadeDuration: 300
  };

  let indicator = null;
  let timerId = null;
  let currentConfig = { ...defaultConfig };

  function createIndicator() {
    const div = document.createElement('div');
    div.style.cssText = `
      position: fixed;
      width: ${currentConfig.size};
      height: ${currentConfig.size};
      bottom: ${currentConfig.position.bottom};
      right: ${currentConfig.position.right};
      z-index: ${currentConfig.zIndex};
      background: url('${currentConfig.imageUrl}') center/contain no-repeat;
      opacity: 0;
      transition: opacity ${currentConfig.fadeDuration}ms;
    `;
    return div;
  }

  function show() {
    if (indicator) return;
    
    indicator = createIndicator();
    document.body.appendChild(indicator);
    
    setTimeout(() => {
      indicator.style.opacity = '1';
    }, 10);
    
    timerId = setTimeout(hide, currentConfig.timeout);
  }

  function hide() {
    if (!indicator) return;
    
    indicator.style.opacity = '0';
    setTimeout(() => {
      indicator.remove();
      indicator = null;
    }, currentConfig.fadeDuration);
    
    clearTimeout(timerId);
  }

  function updateConfig(newConfig) {
    currentConfig = { ...defaultConfig, ...newConfig };
  }

  document.addEventListener('DOMContentLoaded', show);
  window.addEventListener('load', hide);

  return {
    show,
    hide,
    updateConfig
  };
})();