GitHub Style

智能高亮 GitHub 活跃仓库:根据最后更新时间自动标记颜色,支持文件按时间排序、自定义主题、显示精确时间。

2026-03-13 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name           GitHub Style
// @namespace      http://tampermonkey.net/
// @version        1.0.4
// @description    智能高亮 GitHub 活跃仓库:根据最后更新时间自动标记颜色,支持文件按时间排序、自定义主题、显示精确时间。
// @author         XiMenChuiXue
// @license        MIT
// @icon           https://raw.githubusercontent.com/rational-stars/picgo/refs/heads/main/avatar.jpg
// @match          https://github.com/*
// @match          https://github.com/*/*
// @match          https://github.com/*/*/*
// @match          https://github.com/*/*/*/*
// @exclude        https://github.com/settings/*
// @exclude        https://github.com/notifications/*
// @exclude        https://github.com/marketplace/*
// @grant          GM_registerMenuCommand
// @grant          GM_setValue
// @grant          GM_getValue
// @grant          GM_addStyle
// @grant          GM_getResourceText
// @run-at         document-start
// @require        https://code.jquery.com/jquery-3.6.0.min.js#sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=
// @require        https://cdn.jsdelivr.net/npm/sweetalert2@11
// @require        https://cdn.jsdelivr.net/npm/@simonwep/[email protected]/dist/pickr.min.js
// @require        https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js
// @resource       PICKR_CSS https://cdn.jsdelivr.net/npm/@simonwep/[email protected]/dist/themes/classic.min.css
// ==/UserScript==

/* global luxon, Pickr, Swal, $ */
(function () {
    'use strict';

    const DateTime = luxon.DateTime;

    // 注入 Pickr CSS
    const pickrCss = GM_getResourceText('PICKR_CSS');
    if (pickrCss) GM_addStyle(pickrCss);

    // Styles - 修复下拉菜单样式
    GM_addStyle(`
      .swal2-popup.swal2-modal.swal2-show {
          color: #FFF;
          border-radius: 20px;
          background: #31b96c;
          box-shadow: 8px 8px 16px #217e49, -8px -8px 16px #41f48f;
      }
      .formatted-date-span {
          white-space: nowrap !important;
          display: inline-block !important;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif !important;
          font-size: 14px !important;
          vertical-align: middle;
      }
      div[role="row"] > div:last-child,
      td[role="gridcell"]:last-child {
          min-width: 135px !important;
          text-align: right !important;
          display: flex !important;
          align-items: center;
          justify-content: flex-end;
          padding-right: 10px !important;
      }
      #swal2-title a {
          display: inline-block;
          height: 40px;
          margin-right: 10px;
          border-radius: 10px;
          overflow: hidden;
          color: #fff;
      }
      #swal2-title {
          display: flex !important;
          justify-content: center;
          align-items: center;
      }

      /* 修复下拉菜单样式 - 添加深色背景和边框 */
      .row-box select {
          border: 2px solid rgba(255, 255, 255, 0.3);
          border-radius: 8px;
          background-color: rgba(0, 0, 0, 0.2) !important;
          color: #fff !important;
          padding: 8px 12px;
          font-size: 14px;
          cursor: pointer;
          outline: none;
          min-width: 120px;
      }

      .row-box select:hover {
          border-color: rgba(255, 255, 255, 0.5);
          background-color: rgba(0, 0, 0, 0.3) !important;
      }

      .row-box select:focus {
          border-color: rgba(255, 255, 255, 0.8);
          background-color: rgba(0, 0, 0, 0.4) !important;
          box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
      }

      /* 下拉选项样式 */
      .row-box select option {
          background-color: #2d3748 !important;
          color: #fff !important;
          padding: 8px;
      }

      /* 针对 Firefox 的特殊处理 */
      .row-box select:-moz-focusring {
          color: transparent;
          text-shadow: 0 0 0 #fff;
      }

      /* 针对 Webkit 浏览器的下拉箭头 */
      .row-box select {
          -webkit-appearance: none;
          -moz-appearance: none;
          appearance: none;
          background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
          background-repeat: no-repeat;
          background-position: right 8px center;
          background-size: 16px;
          padding-right: 32px;
      }

      .row-box {
          display: flex;
          margin: 20px 25px;
          align-items: center;
          justify-content: space-between;
      }
      .row-box .swal2-input {
          height: 40px;
          background-color: rgba(0, 0, 0, 0.2) !important;
          color: #fff !important;
          border: 2px solid rgba(255, 255, 255, 0.3);
          border-radius: 8px;
      }
      .row-box .swal2-input:focus {
          border-color: rgba(255, 255, 255, 0.8);
          box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
      }
      .row-box label {
          margin-right: 10px;
          color: #fff;
          font-weight: 500;
      }
      .row-box main input {
          background: rgba(15, 172, 83, 1);
          width: 70px;
          border: unset;
          box-shadow: unset;
          text-align: right;
          margin: 0;
      }
      .row-box main {
          display: flex;
          align-items: center;
          gap: 10px;
      }
      .freshness-stars { padding: 8px; }
      .freshness-updated { margin-left: 5px; }
      .freshness-force-color { color: inherit !important; }
      .github-freshness-loading {
          opacity: 0.6;
          pointer-events: none;
      }

      /* 复选框样式优化 */
      input[type="checkbox"] {
          width: 18px;
          height: 18px;
          cursor: pointer;
          accent-color: #0fac53;
      }

      /* 数字输入框样式 */
      input[type="number"] {
          background-color: rgba(0, 0, 0, 0.2) !important;
          color: #fff !important;
          border: 2px solid rgba(255, 255, 255, 0.3) !important;
          border-radius: 8px;
          padding: 8px;
          text-align: center;
      }

      input[type="number"]:focus {
          border-color: rgba(255, 255, 255, 0.8) !important;
          outline: none;
      }

      /* 密码输入框样式 */
      input[type="password"] {
          background-color: rgba(0, 0, 0, 0.2) !important;
          color: #fff !important;
          border: 2px solid rgba(255, 255, 255, 0.3) !important;
          border-radius: 8px;
          padding: 8px 12px;
          min-width: 200px;
      }

      input[type="password"]:focus {
          border-color: rgba(255, 255, 255, 0.8) !important;
          outline: none;
          box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
      }

      /* 按钮样式优化 */
      #RESET_DEFAULTS {
          background-color: #e53e3e !important;
          color: white !important;
          border: none !important;
          padding: 10px 24px !important;
          border-radius: 8px !important;
          cursor: pointer;
          font-weight: 500;
          transition: all 0.2s;
      }

      #RESET_DEFAULTS:hover {
          background-color: #c53030 !important;
          transform: translateY(-1px);
          box-shadow: 0 4px 12px rgba(229, 62, 62, 0.4);
      }

      /* SweetAlert2 按钮样式 */
      .swal2-confirm {
          background-color: #0fac53 !important;
          border-radius: 8px !important;
      }

      .swal2-cancel {
          background-color: #718096 !important;
          border-radius: 8px !important;
      }
    `);

    const PanelDom = `
      <div class="row-box">
          <label for="THEME-select">主题设置:</label>
          <main>
              <select tabindex="-1" id="THEME-select" class="swal2-input">
                  <option value="light">☀️ Light</option>
                  <option value="dark">🌙 Dark</option>
              </select>
          </main>
      </div>
      <div class="row-box">
          <label id="TIME_BOUNDARY-label">时间阈值:</label>
          <main>
              <input id="TIME_BOUNDARY-number" type="number" class="swal2-input" value="" min="1" max="999" maxlength="3" pattern="\\d{1,3}">
              <select tabindex="-1" id="TIME_BOUNDARY-select" class="swal2-input">
                  <option value="day">📅 日</option>
                  <option value="week">📆 周</option>
                  <option value="month">🗓️ 月</option>
                  <option value="year">📊 年</option>
              </select>
          </main>
      </div>
      <div class="row-box">
          <div><label id="BGC-label">🎨 背景颜色:</label><input type="checkbox" id="BGC-enabled"></div>
          <main>
              <span id="BGC-highlight-color-value"><div id="BGC-highlight-color-pickr"></div></span>
              <span id="BGC-grey-color-value"><div id="BGC-grey-color-pickr"></div></span>
          </main>
      </div>
      <div class="row-box">
          <div><label id="FONT-label">🔤 字体颜色:</label><input type="checkbox" id="FONT-enabled"></div>
          <main>
              <span id="FONT-highlight-color-value"><div id="FONT-highlight-color-pickr"></div></span>
              <span id="FONT-grey-color-value"><div id="FONT-grey-color-pickr"></div></span>
          </main>
      </div>
      <div class="row-box">
          <div><label id="DIR-label">📁 文件夹颜色:</label><input type="checkbox" id="DIR-enabled"></div>
          <main>
              <span id="DIR-highlight-color-value"><div id="DIR-highlight-color-pickr"></div></span>
              <span id="DIR-grey-color-value"><div id="DIR-grey-color-pickr"></div></span>
          </main>
      </div>
      <div class="row-box">
          <div><label id="TIME_FORMAT-label">🕐 时间格式化:</label><input type="checkbox" id="TIME_FORMAT-enabled"></div>
      </div>
      <div class="row-box">
          <div><label id="SORT-label">🔀 文件排序:</label><input type="checkbox" id="SORT-enabled"></div>
          <main>
              <select tabindex="-1" id="SORT-select" class="swal2-input">
                  <option value="asc">⬆️ 时间正序</option>
                  <option value="desc">⬇️ 时间倒序</option>
              </select>
          </main>
      </div>
      <div class="row-box">
          <label for="CURRENT_THEME-select">🌓 当前主题:</label>
          <main>
              <select tabindex="-1" id="CURRENT_THEME-select" class="swal2-input">
                  <option value="auto">⚡ Auto</option>
                  <option value="light">☀️ Light</option>
                  <option value="dark">🌙 Dark</option>
              </select>
          </main>
      </div>
      <div class="row-box">
          <div>
              <label id="AWESOME-label"><a target="_blank" href="https://github.com/settings/tokens" style="color: #fff; text-decoration: underline;">🔑 AWESOME token:</a></label>
              <input type="checkbox" id="AWESOME-enabled">
          </div>
          <main>
              <input id="AWESOME_TOKEN" type="password" class="swal2-input" value="" placeholder="ghp_xxxxxxxxxxxx">
          </main>
      </div>
      <div class="row-box" style="justify-content: center; margin-top: 30px;">
          <button type="button" id="RESET_DEFAULTS" class="swal2-styled">🔄 恢复默认设置</button>
      </div>
      <p style="font-size: 0.85em; opacity: 0.9; text-align: center; margin-top: 15px; color: rgba(255,255,255,0.8);">
          💡 提示:复选框切换需刷新页面生效
      </p>
    `;

    // Configuration
    const default_THEME = {
        BGC: { highlightColor: 'rgba(15, 172, 83, 1)', greyColor: 'rgba(245, 245, 245, 0.24)', isEnabled: true },
        TIME_BOUNDARY: { number: 30, select: 'day' },
        FONT: { highlightColor: 'rgba(252, 252, 252, 1)', greyColor: 'rgba(0, 0, 0, 1)', isEnabled: true },
        DIR: { highlightColor: 'rgba(15, 172, 83, 1)', greyColor: 'rgba(154, 154, 154, 1)', isEnabled: true },
        SORT: { select: 'desc', isEnabled: true },
        AWESOME: { isEnabled: false },
        TIME_FORMAT: { isEnabled: true },
    };

    let CURRENT_THEME = GM_getValue('CURRENT_THEME', 'light');
    let AWESOME_TOKEN = GM_getValue('AWESOME_TOKEN', '');
    let THEME_TYPE = getThemeType();
    const config_JSON = JSON.parse(GM_getValue('config_JSON', JSON.stringify({ light: default_THEME, dark: default_THEME })));
    let THEME = config_JSON[THEME_TYPE] || JSON.parse(JSON.stringify(default_THEME));

    // API 请求缓存和队列
    const apiCache = new Map();
    const apiQueue = [];
    let apiProcessing = false;
    const API_DELAY = 100;

    const configPickr = {
        theme: 'monolith',
        components: {
            preview: true, opacity: true, hue: true,
            interaction: { rgba: true, input: true, clear: true, save: true },
        },
    };

    // Helper Functions
    function getThemeType() {
        let themeType = CURRENT_THEME;
        if (CURRENT_THEME === 'auto') {
            themeType = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
        }
        return themeType;
    }

    function initPickr(el_default) {
        const pickr = Pickr.create({ ...configPickr, ...el_default });
        pickr.on('save', (color, instance) => {
            pickr.hide();
        });
        return pickr;
    }

    function getUpdatedThemeConfig() {
        let updatedTheme = {};
        for (const [themeKey, themeVal] of Object.entries(default_THEME)) {
            updatedTheme[themeKey] = {};
            for (let [key, val] of Object.entries(themeVal)) {
                if (key === 'highlightColor' || key === 'greyColor') {
                    const type = key === 'highlightColor' ? 'highlight' : 'grey';
                    const colorVal = $(`#${themeKey}-${type}-color-value .pcr-button`).css('--pcr-color');
                    val = colorVal || val;
                } else if (key === 'isEnabled') {
                    val = $(`#${themeKey}-enabled`).prop('checked');
                } else if (key === 'number' || key === 'select') {
                    val = $(`#${themeKey}-${key}`).val();
                    if (key === 'number') val = parseInt(val) || 30;
                }
                updatedTheme[themeKey][key] = val;
            }
        }
        return updatedTheme;
    }

    function handleData(theme) {
        if (!theme) return;
        for (const [themeKey, themeVal] of Object.entries(theme)) {
            for (const [key, val] of Object.entries(themeVal)) {
                if (key === 'highlightColor' || key === 'greyColor') {
                    const type = key === 'highlightColor' ? 'highlight' : 'grey';
                    const $btn = $(`#${themeKey}-${type}-color-value .pcr-button`);
                    if ($btn.length && val) {
                        $btn.css('--pcr-color', val);
                    }
                } else if (key === 'isEnabled') {
                    $(`#${themeKey}-enabled`).prop('checked', val);
                } else if (key === 'number' || key === 'select') {
                    $(`#${themeKey}-${key}`).val(val);
                }
            }
        }
    }

    function resetToDefaults() {
        const themeType = $('#THEME-select').val();
        handleData(default_THEME);

        // 重新初始化颜色选择器
        initPickr({ el: '#BGC-highlight-color-pickr', default: default_THEME.BGC.highlightColor });
        initPickr({ el: '#BGC-grey-color-pickr', default: default_THEME.BGC.greyColor });
        initPickr({ el: '#FONT-highlight-color-pickr', default: default_THEME.FONT.highlightColor });
        initPickr({ el: '#FONT-grey-color-pickr', default: default_THEME.FONT.greyColor });
        initPickr({ el: '#DIR-highlight-color-pickr', default: default_THEME.DIR.highlightColor });
        initPickr({ el: '#DIR-grey-color-pickr', default: default_THEME.DIR.greyColor });
    }

    // UI
    function createSettingsPanel() {
        Swal.fire({
            title: `<div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
                <span>🎨</span>
                <span>GitHub Freshness Settings</span>
            </div>`,
            html: PanelDom,
            focusConfirm: false,
            allowOutsideClick: false,
            background: '#31b96c',
            confirmButtonColor: '#0fac53',
            cancelButtonColor: '#718096',
            preConfirm: () => {
                const updated_THEME = getUpdatedThemeConfig();
                CURRENT_THEME = $('#CURRENT_THEME-select').val();
                AWESOME_TOKEN = $('#AWESOME_TOKEN').val().trim();

                if (updated_THEME.AWESOME.isEnabled && AWESOME_TOKEN && !AWESOME_TOKEN.match(/^ghp_[a-zA-Z0-9]{36}$/)) {
                    Swal.showValidationMessage('❌ Token 格式不正确,应为 ghp_ 开头的 40 位字符串');
                    return false;
                }

                GM_setValue('config_JSON', JSON.stringify({
                    ...config_JSON,
                    [$('#THEME-select').val()]: updated_THEME,
                }));
                GM_setValue('CURRENT_THEME', CURRENT_THEME);
                GM_setValue('AWESOME_TOKEN', AWESOME_TOKEN);
                THEME = updated_THEME;
                GitHub_Freshness(updated_THEME);

                Swal.fire({
                    position: 'top-center',
                    background: '#31b96c',
                    icon: 'success',
                    iconColor: '#fff',
                    title: '<span style="color: #fff;">✅ 保存成功!</span>',
                    showConfirmButton: false,
                    timer: 1000,
                    toast: true
                });
            },
            heightAuto: false,
            showCancelButton: true,
            confirmButtonText: '💾 保存',
            cancelButtonText: '❌ 取消',
            didOpen: () => {
                initSettings(THEME);

                $('#THEME-select').on('change', function () {
                    let selectedTheme = $(this).val();
                    let theme = config_JSON[selectedTheme] || JSON.parse(JSON.stringify(default_THEME));
                    handleData(theme);

                    initPickr({ el: '#BGC-highlight-color-pickr', default: theme.BGC.highlightColor });
                    initPickr({ el: '#BGC-grey-color-pickr', default: theme.BGC.greyColor });
                    initPickr({ el: '#FONT-highlight-color-pickr', default: theme.FONT.highlightColor });
                    initPickr({ el: '#FONT-grey-color-pickr', default: theme.FONT.greyColor });
                    initPickr({ el: '#DIR-highlight-color-pickr', default: theme.DIR.highlightColor });
                    initPickr({ el: '#DIR-grey-color-pickr', default: theme.DIR.greyColor });
                });

                $('#RESET_DEFAULTS').on('click', () => {
                    Swal.fire({
                        title: '🔄 确认恢复默认?',
                        text: '当前主题的设置将被重置为默认值',
                        icon: 'warning',
                        iconColor: '#f6e05e',
                        background: '#31b96c',
                        color: '#fff',
                        showCancelButton: true,
                        confirmButtonColor: '#e53e3e',
                        cancelButtonColor: '#718096',
                        confirmButtonText: '⚠️ 确认重置',
                        cancelButtonText: '✋ 取消'
                    }).then((result) => {
                        if (result.isConfirmed) {
                            resetToDefaults();
                            Swal.fire({
                                position: 'top-center',
                                background: '#31b96c',
                                icon: 'success',
                                iconColor: '#fff',
                                title: '<span style="color: #fff;">✅ 已重置为默认值</span>',
                                showConfirmButton: false,
                                timer: 800,
                                toast: true
                            });
                        }
                    });
                });
            }
        });
    }

    function initSettings(theme) {
        if (!theme) theme = JSON.parse(JSON.stringify(default_THEME));

        initPickr({ el: '#BGC-highlight-color-pickr', default: theme.BGC.highlightColor });
        initPickr({ el: '#BGC-grey-color-pickr', default: theme.BGC.greyColor });
        initPickr({ el: '#FONT-highlight-color-pickr', default: theme.FONT.highlightColor });
        initPickr({ el: '#FONT-grey-color-pickr', default: theme.FONT.greyColor });
        initPickr({ el: '#DIR-highlight-color-pickr', default: theme.DIR.highlightColor });
        initPickr({ el: '#DIR-grey-color-pickr', default: theme.DIR.greyColor });

        $('#THEME-select').val(THEME_TYPE);
        $('#CURRENT_THEME-select').val(CURRENT_THEME);
        $('#AWESOME_TOKEN').val(AWESOME_TOKEN);
        handleData(theme);
    }

    // DOM Helpers
    function setElementBGC(el, BGC, timeResult) {
        if (el.length && el[0] && BGC.isEnabled) {
            try {
                el[0].style.setProperty('background-color', timeResult ? BGC.highlightColor : BGC.greyColor, 'important');
            } catch (e) {
                console.warn('GitHub Freshness: Failed to set background color', e);
            }
        }
    }

    function setElementDIR(el, DIR, timeResult) {
        if (el.length && DIR.isEnabled) {
            const color = timeResult ? DIR.highlightColor : DIR.greyColor;
            try {
                if (el[0]) {
                    el[0].style.setProperty('fill', color, 'important');
                    el[0].style.setProperty('stroke', color, 'important');
                }
                el.attr('fill', color);
                el.attr('stroke', color);
            } catch (e) {
                console.warn('GitHub Freshness: Failed to set icon color', e);
            }
        }
    }

    function setElementFONT(el, FONT, timeResult) {
        if (el.length && el[0] && FONT.isEnabled) {
            try {
                el[0].style.setProperty('color', timeResult ? FONT.highlightColor : FONT.greyColor, 'important');
            } catch (e) {
                console.warn('GitHub Freshness: Failed to set font color', e);
            }
        }
    }

    function handleTime(time, time_boundary, type = 'ISO8601') {
        if (!time) return false;

        const { number, select } = time_boundary;
        let days = 0;
        switch (select) {
            case 'day': days = parseInt(number) || 30; break;
            case 'week': days = (parseInt(number) || 4) * 7; break;
            case 'month': days = (parseInt(number) || 1) * 30; break;
            case 'year': days = (parseInt(number) || 1) * 365; break;
            default: days = 30;
        }

        const thresholdDate = new Date();
        thresholdDate.setDate(thresholdDate.getDate() - days);
        thresholdDate.setHours(0, 0, 0, 0);

        let inputDate = null;

        if (type === 'UTC') {
            const formats = [
                "yyyy年M月d日 'GMT'Z HH:mm",
                "MMM d, yyyy, h:mm a 'GMT'Z",
                "MMM d, yyyy 'GMT'Z",
                "yyyy-MM-dd'T'HH:mm:ss'Z'",
                "yyyy-MM-dd HH:mm:ss"
            ];

            for (const fmt of formats) {
                try {
                    const dt = DateTime.fromFormat(time, fmt, { zone: 'UTC' });
                    if (dt.isValid) {
                        inputDate = dt.toJSDate();
                        break;
                    }
                } catch (e) {
                    continue;
                }
            }

            if (!inputDate) {
                const nativeDate = new Date(time);
                if (!isNaN(nativeDate.getTime())) {
                    inputDate = nativeDate;
                }
            }
        } else {
            inputDate = new Date(time);
        }

        if (!inputDate || isNaN(inputDate.getTime())) {
            console.warn('GitHub Freshness: Unable to parse date:', time);
            return false;
        }

        return inputDate >= thresholdDate;
    }

    function toAPIUrl(href) {
        if (!href) return null;
        const match = href.match(/^https:\/\/github\.com\/([^\\/]+)\/([^\\/]+)/);
        return match ? `https://api.github.com/repos/${match[1]}/${match[2]}` : null;
    }

    async function processApiQueue() {
        if (apiProcessing || apiQueue.length === 0) return;
        apiProcessing = true;

        while (apiQueue.length > 0) {
            const { apiHref, callback } = apiQueue.shift();

            if (apiCache.has(apiHref)) {
                callback(apiCache.get(apiHref));
                continue;
            }

            try {
                const response = await fetch(apiHref, {
                    headers: AWESOME_TOKEN ? { 'Authorization': `token ${AWESOME_TOKEN}` } : {}
                });

                if (response.status === 403) {
                    const remaining = response.headers.get('X-RateLimit-Remaining');
                    const resetTime = response.headers.get('X-RateLimit-Reset');
                    if (remaining === '0' && resetTime) {
                        const waitMs = (parseInt(resetTime) * 1000) - Date.now();
                        console.warn(`GitHub API 限流,将在 ${Math.ceil(waitMs/1000)} 秒后重置`);
                    } else {
                        console.warn('GitHub API 限流或 Token 无效');
                    }
                    callback(null);
                } else if (response.ok) {
                    const data = await response.json();
                    apiCache.set(apiHref, data);
                    setTimeout(() => apiCache.delete(apiHref), 5 * 60 * 1000);
                    callback(data);
                } else {
                    console.warn('GitHub API 请求失败:', response.status);
                    callback(null);
                }
            } catch (error) {
                console.error('GitHub API 请求异常:', error);
                callback(null);
            }

            if (apiQueue.length > 0) {
                await new Promise(resolve => setTimeout(resolve, API_DELAY));
            }
        }

        apiProcessing = false;
    }

    function GitHub_FreshnessAwesome(theme) {
        const observers = [];

        const observer = new IntersectionObserver((entries) => {
            entries.forEach(el => {
                if (el.isIntersecting && el.target.getAttribute('data-requested') !== 'true') {
                    const href = $(el.target).attr('href');
                    const apiHref = toAPIUrl(href);
                    if (!apiHref) return;

                    el.target.setAttribute('data-requested', 'true');
                    $(el.target).addClass('github-freshness-loading');

                    apiQueue.push({
                        apiHref: apiHref,
                        callback: (data) => {
                            $(el.target).removeClass('github-freshness-loading');
                            if (!data) return;

                            const timeResult = handleTime(data.updated_at, theme.TIME_BOUNDARY);

                            if (theme.AWESOME.isEnabled) {
                                const $existing = $(el.target).parent().find('.freshness-stars, .freshness-updated');
                                if ($existing.length === 0) {
                                    $(el.target).after(
                                        `<span class="freshness-stars">★${data.stargazers_count.toLocaleString()}</span>` +
                                        `<span class="freshness-updated">📅${formatDate(data.updated_at)}</span>`
                                    );
                                    $(el.target).css('padding', '0 12px');
                                }
                            }

                            setElementBGC($(el.target), theme.BGC, timeResult);
                            setElementFONT($(el.target), theme.FONT, timeResult);
                        }
                    });

                    processApiQueue();
                }
            });
        }, { threshold: 0.1, rootMargin: '50px' });

        $('.Box-row a, .markdown-body a').each(function () {
            const href = $(this).attr('href');
            if (href && /^https:\/\/github\.com\/[^\\/]+\/[^\\/]+\/?$/.test(href)) {
                observer.observe(this);
            }
        });

        observers.push(observer);
        return observers;
    }

    function GitHub_FreshnessSearchPage(theme) {
        const elements = $('relative-time[datetime], relative-time[title]');
        if (elements.length === 0) return;

        elements.each(function () {
            const $this = $(this);
            const datetime = $this.attr('datetime');
            const title = $this.attr('title');

            const timeStr = datetime || title;
            if (!timeStr) return;

            const isISO8601 = !!datetime;
            const timeResult = handleTime(timeStr, theme.TIME_BOUNDARY, isISO8601 ? 'ISO8601' : 'UTC');

            const BGC_element = $this.closest('div[data-testid*="results-list"], div[data-testid*="results-card"], li, article, .Box-row');
            setElementBGC(BGC_element, theme.BGC, timeResult);
            setElementFONT($this, theme.FONT, timeResult);
        });
    }

    function GitHub_Freshness(theme) {
        if (!theme) theme = THEME;
        const matchUrl = isMatchedUrl();
        if (!matchUrl) return;

        if (matchUrl === 'matchSearchPage') {
            GitHub_FreshnessSearchPage(theme);
            return;
        }

        const timeElements = $('[data-testid="file-list-row"] relative-time, .Box-row relative-time, tr relative-time, div[role="row"] relative-time');
        if (timeElements.length === 0) return;

        let trRows = [];
        let validRows = 0;

        timeElements.each(function () {
            const $this = $(this);
            const datetime = $this.attr('datetime');
            if (!datetime) return;

            const timeResult = handleTime(datetime, theme.TIME_BOUNDARY);
            const trElement = $this.closest('[data-testid="file-list-row"], .Box-row, tr, div[role="row"]');

            if (trElement.length) {
                trRows.push({
                    element: trElement[0],
                    datetime: datetime,
                    timeResult: timeResult,
                    $el: trElement
                });
                validRows++;

                let BGC_element = trElement.find('[data-testid="latest-commit-details"], td:last-child, div[role="gridcell"]:last-child').first();
                if (BGC_element.length === 0) BGC_element = $this.parent();

                const ICON_element = trElement.find('svg').first();
                setElementBGC(BGC_element, theme.BGC, timeResult);
                setElementDIR(ICON_element, theme.DIR, timeResult);
                setElementFONT($this.parent(), theme.FONT, timeResult);
            }
        });

        if (theme.SORT.isEnabled && validRows > 1) {
            const parentContainer = $(trRows[0].element).parent();
            if (parentContainer.is('tbody, [role="grid"], [data-testid="file-list-row-container"]')) {
                trRows.sort((a, b) => {
                    const tA = new Date(a.datetime).getTime();
                    const tB = new Date(b.datetime).getTime();
                    if (isNaN(tA) || isNaN(tB)) return 0;
                    return theme.SORT.select === 'asc' ? tA - tB : tB - tA;
                });

                const fragment = document.createDocumentFragment();
                trRows.forEach(row => fragment.appendChild(row.element));
                parentContainer.append(fragment);
            }
        }
    }

    function isMatchedUrl() {
        const href = window.location.href;
        if (/^https:\/\/github\.com\/search\?.*$/.test(href)) return 'matchSearchPage';
        if (/^https:\/\/github\.com\/[^/]+\/[^/]+(?:\?.*)?$|^https:\/\/github\.com\/[^/]+\/[^/]+\/tree\/.+$/.test(href)) return 'matchRepoPage';
        return null;
    }

    function formatDate(dateStr) {
        if (!dateStr) return '';
        const date = new Date(dateStr);
        if (isNaN(date.getTime())) return dateStr;

        const Y = date.getFullYear();
        const M = String(date.getMonth() + 1).padStart(2, '0');
        const D = String(date.getDate()).padStart(2, '0');
        return `${Y}-${M}-${D}`;
    }

    function forceFormatRelativeTime() {
        if (!THEME.TIME_FORMAT.isEnabled) return;

        const elements = document.querySelectorAll('relative-time');
        if (elements.length === 0) return;

        elements.forEach((item) => {
            try {
                const dateStr = item.getAttribute('datetime');
                if (!dateStr) return;

                const date = new Date(dateStr);
                if (isNaN(date.getTime())) return;

                const Y = date.getFullYear();
                const M = String(date.getMonth() + 1).padStart(2, '0');
                const D = String(date.getDate()).padStart(2, '0');
                const hh = String(date.getHours()).padStart(2, '0');
                const mm = String(date.getMinutes()).padStart(2, '0');
                const str = `${Y}-${M}-${D} ${hh}:${mm}`;

                if (item.shadowRoot) {
                    const slot = item.shadowRoot.querySelector('slot');
                    if (slot) {
                        slot.textContent = str;
                    } else if (item.shadowRoot.textContent !== str) {
                        item.shadowRoot.textContent = str;
                    }
                } else if (item.textContent !== str) {
                    item.textContent = str;
                }

                item.removeAttribute('prefix');
                item.setAttribute('format', 'manual');
                item.setAttribute('datetime', dateStr);
            } catch (e) {
                console.warn('GitHub Freshness: Error formatting time', e);
            }
        });
    }

    function debounce(func, wait) {
        let timeout;
        return function (...args) {
            clearTimeout(timeout);
            timeout = setTimeout(() => func.apply(this, args), wait);
        };
    }

    const runScript = debounce(() => {
        try {
            GitHub_Freshness();
            forceFormatRelativeTime();
        } catch (e) {
            console.error('GitHub Freshness: Script execution error', e);
        }
    }, 350);

    let mutationObserver = null;
    function initMutationObserver() {
        if (mutationObserver) return;

        mutationObserver = new MutationObserver((mutations) => {
            let shouldRun = false;
            for (const mutation of mutations) {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    for (const node of mutation.addedNodes) {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.matches && (node.matches('relative-time') ||
                                                 node.querySelector && node.querySelector('relative-time, [data-testid="file-list-row"]'))) {
                                shouldRun = true;
                                break;
                            }
                        }
                    }
                }
            }
            if (shouldRun) {
                runScript();
            }
        });

        mutationObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    $(function() {
        console.log('GitHub Freshness v2.0.1 (UI Fixed) Loaded');
        runScript();
        initMutationObserver();
    });

    function cleanup() {
        if (mutationObserver) {
            mutationObserver.disconnect();
            mutationObserver = null;
        }
    }

    document.addEventListener('pjax:end', () => {
        runScript();
        initMutationObserver();
    });

    window.addEventListener('popstate', () => setTimeout(runScript, 350));

    const originalPush = history.pushState;
    history.pushState = function (...args) {
        originalPush.apply(this, args);
        setTimeout(runScript, 350);
    };

    const originalReplace = history.replaceState;
    history.replaceState = function (...args) {
        originalReplace.apply(this, args);
        setTimeout(runScript, 350);
    };

    let timeUpdateScheduled = false;
    function scheduleTimeUpdate() {
        if (timeUpdateScheduled) return;
        timeUpdateScheduled = true;
        requestAnimationFrame(() => {
            forceFormatRelativeTime();
            timeUpdateScheduled = false;
        });
    }

    setInterval(scheduleTimeUpdate, 2000);

    GM_registerMenuCommand('⚙️ Settings', createSettingsPanel);

    const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    darkModeMediaQuery.addEventListener('change', (e) => {
        if (CURRENT_THEME === 'auto') {
            THEME_TYPE = e.matches ? 'dark' : 'light';
            THEME = config_JSON[THEME_TYPE] || JSON.parse(JSON.stringify(default_THEME));
            runScript();
        }
    });

    window.addEventListener('beforeunload', cleanup);
})();