// ==UserScript==
// @name GitHub 文件高亮 - 优化版
// @namespace http://tampermonkey.net/
// @version 0.24
// @description 实现GitHub文件列表按时间倒序排列,高亮30天内更新的文件(背景色),支持鼠标悬停加深背景,支持多主题切换
// @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 THEMES = {
'default': {
name: '默认淡蓝色',
BGC: {
highlight: 'rgba(40, 135, 255, 0.15)',
grey: 'rgba(200, 200, 200, 0.1)',
highlightHover: 'rgba(40, 135, 255, 0.3)',
greyHover: 'rgba(200, 200, 200, 0.25)'
}
},
'green': {
name: '代码绿色',
BGC: {
highlight: 'rgba(60, 135, 80, 0.15)',
grey: 'rgba(200, 200, 200, 0.1)',
highlightHover: 'rgba(60, 135, 80, 0.3)',
greyHover: 'rgba(200, 200, 200, 0.25)'
}
},
'purple': {
name: '优雅紫色',
BGC: {
highlight: 'rgba(147, 112, 219, 0.15)',
grey: 'rgba(200, 200, 200, 0.1)',
highlightHover: 'rgba(147, 112, 219, 0.3)',
greyHover: 'rgba(200, 200, 200, 0.25)'
}
},
'orange': {
name: '活力橙色',
BGC: {
highlight: 'rgba(255, 147, 0, 0.15)',
grey: 'rgba(200, 200, 200, 0.1)',
highlightHover: 'rgba(255, 147, 0, 0.3)',
greyHover: 'rgba(200, 200, 200, 0.25)'
}
},
'teal': {
name: '青色调',
BGC: {
highlight: 'rgba(32, 201, 151, 0.15)',
grey: 'rgba(200, 200, 200, 0.1)',
highlightHover: 'rgba(32, 201, 151, 0.3)',
greyHover: 'rgba(200, 200, 200, 0.25)'
}
}
};
// 时间阈值(30天)
const TIME_BOUNDARY = { number: 30, unit: 'day' };
// 获取/设置当前主题
let currentTheme = GM_getValue('currentTheme', 'default');
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 setElementStyles(el, timeResult) {
if (el.length) {
// 设置默认背景色(为 td 元素设置)
const defaultBgColor = timeResult ? COLORS.BGC.highlight : COLORS.BGC.grey;
const tdElements = el.find('td');
if (tdElements.length) {
tdElements.each(function() {
this.style.setProperty('background-color', defaultBgColor, 'important');
});
}
// 添加类名以标记高亮状态(用于悬停时判断)
el.removeClass('highlight-row non-highlight-row');
el.addClass(timeResult ? 'highlight-row' : 'non-highlight-row');
// 绑定鼠标悬停事件
el.off('mouseenter mouseleave'); // 移除旧的事件监听,防止重复绑定
el.on('mouseenter', function() {
const hoverBgColor = $(this).hasClass('highlight-row') ? COLORS.BGC.highlightHover : COLORS.BGC.greyHover;
const tdEls = $(this).find('td');
if (tdEls.length) {
tdEls.each(function() {
this.style.setProperty('background-color', hoverBgColor, 'important');
});
}
});
el.on('mouseleave', function() {
const tdEls = $(this).find('td');
if (tdEls.length) {
tdEls.each(function() {
this.style.setProperty('background-color', defaultBgColor, 'important');
});
}
});
}
}
// 主函数:实现文件倒序排列、高亮和鼠标悬停效果
function GitHub_Freshness() {
if (!isMatchedUrl()) return;
const elements = $('relative-time'); // 直接使用 <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');
trRows.push(trElement[0]);
setElementStyles(trElement, timeResult);
}
});
if (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('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();
}
// 添加油猴菜单
GM_registerMenuCommand(`当前主题:${THEMES[currentTheme].name}`, () => {}, '0');
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 ($('relative-time').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);
})();