智能高亮 GitHub 活跃仓库:集成 CSS 视觉排序、配置导入/导出,彻底解决 React 冲突与时间闪烁。
נכון ליום
// ==UserScript== // @name GitHub Style // @namespace http://tampermonkey.net/ // @version 1.0.8 // @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; } .gh-freshness-flex-sort { display: flex !important; flex-direction: column !important; } .gh-freshness-flex-sort > [role="row"], .gh-freshness-flex-sort > .Box-row, .gh-freshness-flex-sort > div { width: 100% !important; box-sizing: border-box !important; margin-left: 0 !important; margin-right: 0 !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">🔀 文件排序 (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> `; 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 以支持运行时导入覆盖 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 }, }, }; 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 }); } 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); } 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) {} } } 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; } 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; } 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); } }); } 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; 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"]'); 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); }); applyCSSSortOrder(theme); } function applyCSSSortOrder(theme) { if (!theme.SORT.isEnabled) return; const containers = document.querySelectorAll('tbody, [role="grid"], [data-testid="file-list-row-container"], .Box'); containers.forEach(container => { if (container.tagName === 'TBODY' || container.tagName === 'TABLE') return; const rows = container.querySelectorAll(':scope > [role="row"], :scope > .Box-row, :scope > div'); if (rows.length <= 1) return; container.classList.add('gh-freshness-flex-sort'); rows.forEach(row => { const timeEl = row.querySelector('relative-time[datetime]'); if (!timeEl) return; const isCommitRow = row.querySelector('[data-testid="latest-commit-details"]') !== null; if (isCommitRow) { row.style.setProperty('order', '-999999999', 'important'); } else { const datetime = timeEl.getAttribute('datetime'); const timestamp = new Date(datetime).getTime(); if (!isNaN(timestamp)) { const orderVal = theme.SORT.select === 'desc' ? -timestamp : timestamp; row.style.setProperty('order', String(orderVal), 'important'); } } }); }); } 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.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) {} }); } 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); 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 v1.0.8 (Config Sync) 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); }; let timeUpdateScheduled = false; function scheduleTimeUpdate() { if (timeUpdateScheduled) return; timeUpdateScheduled = true; requestAnimationFrame(() => { forceFormatRelativeTime(); if(THEME.SORT.isEnabled) applyCSSSortOrder(THEME); 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(); } }); })();