GitHub Style

智能高亮 GitHub 活跃仓库:集成 CSS 视觉排序、配置导入/导出,彻底解决 React 冲突与时间闪烁。

Version au 20/07/2026. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name           GitHub Style
// @namespace      http://tampermonkey.net/
// @version        1.0.9
// @description    智能高亮 GitHub 活跃仓库:集成 CSS 视觉排序、配置导入/导出,彻底解决 React 冲突与时间闪烁。
// @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;

    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, .custom-time-format {
          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;
          -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 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; }
      .row-box select:-moz-focusring { color: transparent; text-shadow: 0 0 0 #fff; }

      .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; }
      .github-freshness-loading { opacity: 0.6; pointer-events: none; }

      input[type="checkbox"] { width: 18px; height: 18px; cursor: pointer; accent-color: #0fac53; }
      input[type="number"], 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; text-align: center;
      }
      input[type="password"] { min-width: 200px; text-align: left; padding: 8px 12px; }
      input[type="number"]:focus, 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); }
      .swal2-confirm { background-color: #0fac53 !important; border-radius: 8px !important; }
      .swal2-cancel { background-color: #718096 !important; border-radius: 8px !important; }

      relative-time.custom-time-replaced { display: none !important; }

      /* ✅ 修复: 更健壮的 flex 排序样式 */
      .gh-freshness-flex-sort {
          display: flex !important;
          flex-direction: column !important;
      }
      .gh-freshness-flex-sort > [role="row"],
      .gh-freshness-flex-sort > [data-testid="file-list-row"],
      .gh-freshness-flex-sort > .Box-row {
          width: 100% !important;
          box-sizing: border-box !important;
          margin-left: 0 !important;
          margin-right: 0 !important;
          flex-shrink: 0 !important;
      }
    `);

    // ==================== Panel DOM ====================
    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">🔀 文件排序 (CSS视觉排序):</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_xxx 或 github_pat_xxx">
          </main>
      </div>

      <div class="row-box" style="justify-content: center; gap: 15px; margin-top: 25px; flex-wrap: wrap;">
          <button type="button" id="EXPORT_CONFIG" class="swal2-styled" style="background-color: #3182ce !important; border-radius: 8px !important; margin: 5px; padding: 10px 20px !important;">📤 导出配置</button>
          <button type="button" id="IMPORT_CONFIG" class="swal2-styled" style="background-color: #805ad5 !important; border-radius: 8px !important; margin: 5px; padding: 10px 20px !important;">📥 导入配置</button>
      </div>

      <div class="row-box" style="justify-content: center; margin-top: 15px;">
          <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);">
          💡 提示:排序采用 CSS Flex 黑科技,不破坏 React 架构。配置码包含 Token,请私密分享。
      </p>
    `;

    // ==================== Default Config ====================
    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();
    let 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));

    const apiCache = new Map();
    const apiQueue = [];
    let apiProcessing = false;
    const API_DELAY = 100;

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

    // ==================== Utility 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) => {
            const hex = color.toHEXA().toString();
            instance.options.el.setAttribute('data-pickr-color', hex);
            pickr.hide();
        });
        setTimeout(() => {
            const el = document.querySelector(el_default.el + ' .pcr-button');
            if (el && el_default.default) {
                el.setAttribute('data-pickr-color', el_default.default);
            }
        }, 300);
        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 $btn = $(`#${themeKey}-${type}-color-value .pcr-button`);
                    let colorVal = $btn.attr('data-pickr-color');
                    if (!colorVal) colorVal = $btn.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);
                        $btn.attr('data-pickr-color', val);
                    }
                } else if (key === 'isEnabled') {
                    $(`#${themeKey}-enabled`).prop('checked', val);
                } else if (key === 'number' || key === 'select') {
                    $(`#${themeKey}-${key}`).val(val);
                }
            }
        }
    }

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

    // ==================== Settings Panel ====================
    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();

                const tokenRegex = /^(ghp_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9_]+)$/;
                if (updated_THEME.AWESOME.isEnabled && AWESOME_TOKEN && !AWESOME_TOKEN.match(tokenRegex)) {
                    Swal.showValidationMessage('❌ Token 格式不正确 (支持 ghp_ 或 github_pat_)');
                    return false;
                }

                config_JSON[$('#THEME-select').val()] = updated_THEME;
                GM_setValue('config_JSON', JSON.stringify(config_JSON));
                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
                            });
                        }
                    });
                });

                // 导出配置
                $('#EXPORT_CONFIG').on('click', () => {
                    const exportData = {
                        config_JSON: config_JSON,
                        CURRENT_THEME: CURRENT_THEME,
                        AWESOME_TOKEN: AWESOME_TOKEN
                    };
                    const b64 = btoa(unescape(encodeURIComponent(JSON.stringify(exportData))));

                    Swal.fire({
                        title: '📤 导出配置',
                        html: `
                            <p style="color: #fff; font-size: 14px; margin-bottom: 10px;">⚠️ 包含 Token 等敏感信息,请妥善保管!</p>
                            <textarea id="export-text" style="width: 100%; height: 150px; background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.3); border-radius: 8px; padding: 10px; font-family: monospace; resize: none;">${b64}</textarea>
                        `,
                        background: '#31b96c',
                        color: '#fff',
                        showCancelButton: true,
                        confirmButtonText: '📋 复制并关闭',
                        cancelButtonText: '❌ 关闭',
                        didOpen: () => {
                            const textarea = document.getElementById('export-text');
                            if (textarea) textarea.select();
                        },
                        preConfirm: () => {
                            const textarea = document.getElementById('export-text');
                            if (textarea) {
                                textarea.select();
                                if (navigator.clipboard) {
                                    navigator.clipboard.writeText(textarea.value).catch(() => document.execCommand('copy'));
                                } else {
                                    document.execCommand('copy');
                                }
                            }
                            return true;
                        }
                    });
                });

                // 导入配置
                $('#IMPORT_CONFIG').on('click', () => {
                    Swal.fire({
                        title: '📥 导入配置',
                        html: `
                            <p style="color: #fff; font-size: 14px; margin-bottom: 10px;">请粘贴之前导出的配置码 (Base64):</p>
                            <textarea id="import-text" style="width: 100%; height: 150px; background: rgba(0,0,0,0.3); color: #fff; border: 1px solid rgba(255,255,255,0.3); border-radius: 8px; padding: 10px; font-family: monospace; resize: none;" placeholder="粘贴配置码..."></textarea>
                        `,
                        background: '#31b96c',
                        color: '#fff',
                        showCancelButton: true,
                        confirmButtonText: '✅ 确认导入',
                        cancelButtonText: '❌ 取消',
                        preConfirm: () => {
                            const val = document.getElementById('import-text').value.trim();
                            if (!val) {
                                Swal.showValidationMessage('❌ 配置码不能为空');
                                return false;
                            }
                            try {
                                const jsonStr = decodeURIComponent(escape(atob(val)));
                                const importData = JSON.parse(jsonStr);
                                if (!importData.config_JSON) throw new Error('Invalid format');
                                return importData;
                            } catch (e) {
                                Swal.showValidationMessage('❌ 配置码解析失败,请检查格式');
                                return false;
                            }
                        }
                    }).then((result) => {
                        if (result.isConfirmed && result.value) {
                            const importData = result.value;

                            config_JSON = importData.config_JSON;
                            GM_setValue('config_JSON', JSON.stringify(config_JSON));

                            if (importData.CURRENT_THEME) {
                                CURRENT_THEME = importData.CURRENT_THEME;
                                GM_setValue('CURRENT_THEME', CURRENT_THEME);
                            }
                            if (importData.AWESOME_TOKEN !== undefined) {
                                AWESOME_TOKEN = importData.AWESOME_TOKEN;
                                GM_setValue('AWESOME_TOKEN', AWESOME_TOKEN);
                            }

                            THEME_TYPE = getThemeType();
                            THEME = config_JSON[THEME_TYPE] || JSON.parse(JSON.stringify(default_THEME));

                            Swal.fire({
                                position: 'top-center',
                                background: '#31b96c',
                                icon: 'success',
                                iconColor: '#fff',
                                title: '<span style="color: #fff;">✅ 导入成功!正在刷新...</span>',
                                showConfirmButton: false,
                                timer: 1000,
                                toast: true
                            }).then(() => {
                                Swal.close();
                                createSettingsPanel();
                                runScript();
                            });
                        }
                    });
                });
            }
        });
    }

    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);
    }

    // ==================== Element Styling ====================
    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) {}
        }
    }

    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) {}
        }
    }

    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) {}
        }
    }

    // ==================== Time Handling ====================
    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())) return false;
        return inputDate >= thresholdDate;
    }

    // ==================== API ====================
    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) { 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 { callback(null); }
            } catch (error) { callback(null); }
            if (apiQueue.length > 0) await new Promise(resolve => setTimeout(resolve, API_DELAY));
        }
        apiProcessing = false;
    }

    // ==================== Awesome (Star/Date on repo links) ====================
    function GitHub_FreshnessAwesome(theme) {
        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);
            }
        });
    }

    // ==================== Search Page ====================
    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);
        });
    }

    // ==================== ✅ 修复: 排序辅助函数 ====================

    /**
     * 判断是否为特殊行(commit 信息行、表头行等),需要始终置顶
     */
    function isSpecialRow(row) {
        if (!row) return false;
        // 多种检测方式兼容不同版本的 GitHub DOM
        if (row.querySelector('[data-testid="latest-commit-details"]')) return true;
        if (row.querySelector('[data-testid="latest-commit"]')) return true;
        if (row.querySelector('.react-directory-commit-row')) return true;
        if (row.getAttribute && row.getAttribute('role') === 'columnheader') return true;
        if (row.querySelector('[role="columnheader"]')) return true;
        // 表头行通常没有 relative-time
        if (!row.querySelector('relative-time[datetime]')) return true;
        // 包含 "Latest commit" 或 "Name" 表头文本
        const text = (row.textContent || '').trim();
        if (/^Latest commit/i.test(text)) return true;
        if (/^Name\s/.test(text) && text.length < 50) return true;
        return false;
    }

    /**
     * 从行中提取时间戳(毫秒)
     */
    function getRowTimestamp(row) {
        if (!row) return null;
        const timeEl = row.querySelector('relative-time[datetime]');
        if (!timeEl) return null;
        const datetime = timeEl.getAttribute('datetime');
        if (!datetime) return null;
        const ts = new Date(datetime).getTime();
        return isNaN(ts) ? null : ts;
    }

    // ==================== ✅ 修复: 核心排序函数 ====================
    function applyCSSSortOrder(theme) {
        if (!theme || !theme.SORT || !theme.SORT.isEnabled) return;

        // ✅ 修复 #2: 扩展容器选择器,覆盖 GitHub 当前所有可能的文件列表容器
        const containers = document.querySelectorAll(
            '#repos-file-tree, ' +
            '[role="rowgroup"], ' +
            '[role="grid"], ' +
            '[data-testid="repos-file-tree-container"], ' +
            '[data-testid="file-list-row-container"], ' +
            '.react-directory-truncate, ' +
            '.Box-body, ' +
            'table.files tbody, ' +
            'tbody'
        );

        if (containers.length === 0) return;

        containers.forEach(container => {
            const tag = container.tagName.toUpperCase();
            let rows;

            // ✅ 修复 #3: 根据容器类型使用不同的行选择器
            if (tag === 'TBODY' || tag === 'TABLE') {
                rows = Array.from(container.querySelectorAll(':scope > tr'));
            } else {
                // Div 布局:精确匹配行元素
                rows = Array.from(container.querySelectorAll(
                    ':scope > [role="row"], ' +
                    ':scope > [data-testid="file-list-row"], ' +
                    ':scope > .Box-row'
                ));
                // 回退:带 relative-time 的子 div
                if (rows.length === 0) {
                    rows = Array.from(container.querySelectorAll(':scope > div:has(relative-time[datetime])'));
                }
            }

            if (rows.length <= 1) return;

            // ✅ 修复 #1 & #6: 对 table 布局使用 DOM 重排(flex 会破坏 table 布局)
            if (tag === 'TBODY' || tag === 'TABLE') {
                const sorted = rows.slice().sort((a, b) => {
                    const aSpecial = isSpecialRow(a);
                    const bSpecial = isSpecialRow(b);
                    if (aSpecial && !bSpecial) return -1;
                    if (!aSpecial && bSpecial) return 1;
                    if (aSpecial && bSpecial) return 0;

                    const aTs = getRowTimestamp(a);
                    const bTs = getRowTimestamp(b);
                    if (aTs === null || bTs === null) return 0;
                    return theme.SORT.select === 'desc' ? bTs - aTs : aTs - bTs;
                });
                sorted.forEach(row => container.appendChild(row));
                return;
            }

            // Div 布局:使用 CSS flex order(不破坏 React 架构)
            container.classList.add('gh-freshness-flex-sort');
            rows.forEach(row => {
                // ✅ 修复 #5: 特殊行置顶
                if (isSpecialRow(row)) {
                    row.style.setProperty('order', '-999999999', 'important');
                    return;
                }

                const timestamp = getRowTimestamp(row);
                if (timestamp !== null) {
                    const orderVal = theme.SORT.select === 'desc' ? -timestamp : timestamp;
                    row.style.setProperty('order', String(orderVal), 'important');
                }
            });
        });
    }

    // ==================== ✅ 修复: 主高亮函数 ====================
    function GitHub_Freshness(theme) {
        if (!theme) theme = THEME;
        const matchUrl = isMatchedUrl();
        if (!matchUrl) return;

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

        // ✅ 修复 #4: 扩展时间元素选择器
        const timeElements = $(
            '[data-testid="file-list-row"] relative-time, ' +
            '.Box-row relative-time, ' +
            'tr relative-time, ' +
            'div[role="row"] relative-time, ' +
            '#repos-file-tree relative-time, ' +
            '[role="rowgroup"] relative-time'
        );

        // 高亮处理(仅在找到时间元素时执行)
        if (timeElements.length > 0) {
            timeElements.each(function () {
                const $this = $(this);
                const datetime = $this.attr('datetime');
                if (!datetime) return;
                const trElement = $this.closest(
                    '[data-testid="file-list-row"], .Box-row, tr, div[role="row"], [role="rowgroup"] > div'
                );
                if (!trElement.length) return;
                const timeResult = handleTime(datetime, theme.TIME_BOUNDARY);
                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);
            });
        }

        // ✅ 修复 #4: 排序始终尝试执行,不再依赖 timeElements 是否找到
        applyCSSSortOrder(theme);
    }

    // ==================== URL Matching ====================
    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;
    }

    // ==================== Date Formatting ====================
    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.nextElementSibling && item.nextElementSibling.classList.contains('custom-time-format')) {
                    if (item.nextElementSibling.textContent !== str) item.nextElementSibling.textContent = str;
                    return;
                }
                item.classList.add('custom-time-replaced');
                const span = document.createElement('span');
                span.className = 'custom-time-format formatted-date-span';
                span.textContent = str;
                item.parentNode.insertBefore(span, item.nextSibling);
            } catch (e) {}
        });
    }

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

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

    // ==================== Mutation Observer ====================
    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 });
    }

    // ==================== Init ====================
    $(function() {
        console.log('GitHub Freshness v1.0.9 (Sort Fix) Loaded');
        runScript();
        initMutationObserver();
    });

    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); };

    // ==================== Periodic Time Update ====================
    let timeUpdateScheduled = false;
    function scheduleTimeUpdate() {
        if (timeUpdateScheduled) return;
        timeUpdateScheduled = true;
        requestAnimationFrame(() => {
            forceFormatRelativeTime();
            if (THEME.SORT && THEME.SORT.isEnabled) applyCSSSortOrder(THEME);
            timeUpdateScheduled = false;
        });
    }
    setInterval(scheduleTimeUpdate, 2000);

    // ==================== Menu Command ====================
    GM_registerMenuCommand('⚙️ Settings', createSettingsPanel);

    // ==================== Dark Mode Listener ====================
    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();
        }
    });

})();