// ==UserScript==
// @name GitHub 文件高亮
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 通过颜色高亮显示GitHub仓库更新状态的简化版
// @author Grok
// @icon https://i.miji.bid/2025/03/15/560664f99070e139e28703cf92975c73.jpeg
// @match https://github.com/*/*
// @match https://github.com/*/*/tree/*
// @require https://code.jquery.com/jquery-3.6.0.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// ==/UserScript==
(function() {
'use strict';
const DateTime = luxon.DateTime;
// === 主题配置 ===
const THEMES = {
'default': {
name: '默认绿色',
BGC: { highlight: 'rgba(15, 172, 83, 0.3)', grey: 'rgba(245, 245, 245, 0.24)' },
FONT: { highlight: 'rgba(15, 172, 83, 1)', grey: 'rgba(0, 0, 0, 1)' },
DIR: { highlight: 'rgba(15, 172, 83, 1)', grey: 'rgba(154, 154, 154, 1)' }
},
'blue': {
name: '科技蓝',
BGC: { highlight: 'rgba(0, 121, 255, 0.3)', grey: 'rgba(245, 245, 245, 0.24)' },
FONT: { highlight: 'rgba(0, 121, 255, 1)', grey: 'rgba(0, 0, 0, 1)' },
DIR: { highlight: 'rgba(0, 121, 255, 1)', grey: 'rgba(154, 154, 154, 1)' }
},
'purple': {
name: '优雅紫',
BGC: { highlight: 'rgba(147, 112, 219, 0.3)', grey: 'rgba(245, 245, 245, 0.24)' },
FONT: { highlight: 'rgba(147, 112, 219, 1)', grey: 'rgba(0, 0, 0, 1)' },
DIR: { highlight: 'rgba(147, 112, 219, 1)', grey: 'rgba(154, 154, 154, 1)' }
},
'orange': {
name: '活力橙',
BGC: { highlight: 'rgba(255, 140, 0, 0.3)', grey: 'rgba(245, 245, 245, 0.24)' },
FONT: { highlight: 'rgba(255, 140, 0, 1)', grey: 'rgba(0, 0, 0, 1)' },
DIR: { highlight: 'rgba(255, 140, 0, 1)', grey: 'rgba(154, 154, 154, 1)' }
}
};
// 时间阈值(30天)
const TIME_BOUNDARY = { number: 30, unit: 'day' };
// 获取/设置当前主题和排序开关
let currentTheme = GM_getValue('currentTheme', 'default');
let isSortEnabled = GM_getValue('isSortEnabled', false);
let COLORS = THEMES[currentTheme];
// 处理时间判断
function handelTime(time) {
const now = new Date();
const targetDate = new Date(now);
targetDate.setDate(now.getDate() - TIME_BOUNDARY.number);
return new Date(time) >= targetDate;
}
// 设置样式函数
function setElementBGC(el, timeResult) {
if (el.length) {
el[0].style.setProperty('background-color',
timeResult ? COLORS.BGC.highlight : COLORS.BGC.grey,
'important');
}
}
function setElementFONT(el, timeResult) {
el.css('color', timeResult ? COLORS.FONT.highlight : COLORS.FONT.grey);
}
function setElementDIR(el, timeResult) {
if (el.length) {
el.attr('fill', timeResult ? COLORS.DIR.highlight : COLORS.DIR.grey);
}
}
function setElementTIME_FORMAT(el, datetime) {
if (el.css('display') !== 'none') {
el.css('display', 'none');
const formattedDate = DateTime.fromISO(datetime).toFormat('yyyy-MM-dd');
el.before(`<span>${formattedDate}</span>`);
}
}
// 主函数
function GitHub_Freshness() {
if (!isMatchedUrl()) return;
const elements = $('.sc-aXZVg'); // <relative-time> 元素
if (elements.length === 0) return;
let trRows = [];
elements.each(function() {
const datetime = $(this).attr('datetime');
if (datetime) {
const timeResult = handelTime(datetime);
const trElement = $(this).closest('tr.react-directory-row');
if (isSortEnabled) {
trRows.push(trElement[0]);
}
const BGC_element = $(this).closest('td');
const DIR_element = trElement.find('.icon-directory');
const FILE_element = trElement.find('.color-fg-muted');
setElementBGC(BGC_element, timeResult);
setElementFONT($(this).parent(), timeResult);
setElementDIR(DIR_element, timeResult);
setElementDIR(FILE_element, timeResult);
setElementTIME_FORMAT($(this), datetime);
}
});
if (isSortEnabled && trRows.length > 0) {
trRows.sort((a, b) => {
const dateA = new Date(a.querySelector('relative-time').getAttribute('datetime'));
const dateB = new Date(b.querySelector('relative-time').getAttribute('datetime'));
return dateB - dateA; // 时间倒序
});
const tbody = document.querySelector('.react-directory-filename-column')?.closest('tbody') || document.querySelector('tbody');
if (tbody) {
$(tbody).empty().append(trRows); // 清空并重新追加排序后的行
} else {
console.warn('未找到目标 tbody,排序可能失效');
}
}
}
// URL匹配检查
function isMatchedUrl() {
const currentUrl = window.location.href;
return /^https:\/\/github\.com\/[^/]+\/[^/]+(?:\?.*)?$|^https:\/\/github\.com\/[^/]+\/[^/]+\/tree\/.+$/.test(currentUrl);
}
// 防抖函数
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
const runScript = debounce(GitHub_Freshness, 200);
// 主题切换函数
function switchTheme(themeKey) {
currentTheme = themeKey;
COLORS = THEMES[themeKey];
GM_setValue('currentTheme', themeKey);
GitHub_Freshness();
}
// 排序开关函数
function toggleSort() {
isSortEnabled = !isSortEnabled;
GM_setValue('isSortEnabled', isSortEnabled);
if (isSortEnabled) {
alert('已启用时间倒序排列功能\n注意:此功能可能导致页面加载变慢,尤其在文件较多时');
} else {
alert('已关闭时间倒序排列功能\n文件将按GitHub默认顺序显示,加载速度更快');
}
GitHub_Freshness();
}
// 添加油猴菜单
GM_registerMenuCommand(
`当前状态:${isSortEnabled ? '时间倒序排列开启' : '时间倒序排列关闭'} | 主题:${THEMES[currentTheme].name}`,
() => {}, // 无操作,仅显示状态
'0'
);
GM_registerMenuCommand(
`${isSortEnabled ? '✓ ' : ' '}时间倒序排列`,
toggleSort,
's'
);
for (const [key, theme] of Object.entries(THEMES)) {
GM_registerMenuCommand(
`${currentTheme === key ? '✓ ' : ' '}${theme.name}`,
() => switchTheme(key),
key.substring(0, 1)
);
}
// 立即运行一次
runScript();
// 使用 MutationObserver 监听 DOM 变化
const observer = new MutationObserver(debounce(() => {
if ($('.sc-aXZVg').length > 0) {
runScript();
}
}, 200));
observer.observe(document.body, { childList: true, subtree: true });
// 事件监听
window.addEventListener('load', runScript);
document.addEventListener('pjax:end', runScript);
// 处理 URL 变化
(function(history) {
const pushState = history.pushState;
const replaceState = history.replaceState;
history.pushState = function(state, title, url) {
pushState.apply(history, arguments);
setTimeout(runScript, 200);
};
history.replaceState = function(state, title, url) {
replaceState.apply(history, arguments);
setTimeout(runScript, 200);
};
window.addEventListener('popstate', () => setTimeout(runScript, 200));
})(window.history);
})();