GitHub Commits Time Formatter

GitHub 仓库的 /commits 页面,每个 commit 的头像前面 增加时间 YYYY-MM-DD HH:MM

当前为 2025-03-27 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub Commits Time Formatter
  3. // @namespace http://tampermonkey.net/
  4. // @version 2025-03-19
  5. // @description GitHub 仓库的 /commits 页面,每个 commit 的头像前面 增加时间 YYYY-MM-DD HH:MM
  6. // @author You
  7. // @match https://github.com/*/commits/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=github.com
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // 从 title 属性中提取并格式化时间的函数
  16. function formatDateTimeFromTitle(title) {
  17. const parts = title.match(/(\w+ \d+, \d{4}), (\d+:\d+ [AP]M)/);
  18. if (!parts) {
  19. return "";
  20. }
  21. const [, datePart, timePart] = parts;
  22. const fullDateStr = `${datePart} ${timePart}`;
  23. const date = new Date(fullDateStr);
  24. const year = date.getFullYear();
  25. const month = String(date.getMonth() + 1).padStart(2, '0');
  26. const day = String(date.getDate()).padStart(2, '0');
  27. const hours = String(date.getHours()).padStart(2, '0');
  28. const minutes = String(date.getMinutes()).padStart(2, '0');
  29. return `${year}-${month}-${day} ${hours}:${minutes}`;
  30. }
  31.  
  32. // 处理时间元素的函数
  33. function processTimeElements() {
  34. const relativeTimeElements = document.querySelectorAll('relative-time[class^="sc-aXZVg"][class*="pl-1"]');
  35. relativeTimeElements.forEach((timeElement) => {
  36. const title = timeElement.getAttribute('title');
  37. if (title) {
  38. const formattedTime = formatDateTimeFromTitle(title);
  39. const targetDiv = timeElement.closest('li').querySelector('div[data-testid="author-avatar"]');
  40. if (targetDiv) {
  41. // 检查是否已经存在格式化后的时间元素
  42. const existingTimeSpan = targetDiv.previousElementSibling;
  43. if (existingTimeSpan && existingTimeSpan.tagName === 'SPAN' && existingTimeSpan.textContent === formattedTime) {
  44. return; // 如果已经存在则跳过
  45. }
  46. // 创建新的 <span> 元素来显示格式化后的时间
  47. const timeSpan = document.createElement('span');
  48. timeSpan.textContent = formattedTime;
  49. timeSpan.style.whiteSpace = 'pre'; // 确保空格显示正常
  50. timeSpan.style.marginRight = '10px'; // 添加右边距
  51. // 在指定的 div 元素前面插入新的 <span> 元素
  52. targetDiv.parentNode.insertBefore(timeSpan, targetDiv);
  53. }
  54. }
  55. });
  56. }
  57.  
  58. // 节流函数
  59. function throttle(func, delay) {
  60. let timer = null;
  61. return function() {
  62. if (!timer) {
  63. func.apply(this, arguments);
  64. timer = setTimeout(() => {
  65. timer = null;
  66. }, delay);
  67. }
  68. };
  69. }
  70.  
  71. // 初始化 MutationObserver
  72. function initMutationObserver() {
  73. const throttledProcessTimeElements = throttle(processTimeElements, 500); // 每 500 毫秒最多执行一次
  74. const observer = new MutationObserver((mutationsList) => {
  75. for (const mutation of mutationsList) {
  76. if (mutation.type === 'childList') {
  77. throttledProcessTimeElements();
  78. }
  79. }
  80. });
  81.  
  82. const targetNode = document.body;
  83. const config = { childList: true, subtree: true };
  84.  
  85. observer.observe(targetNode, config);
  86. }
  87.  
  88. // 主函数
  89. function main() {
  90. processTimeElements();
  91. initMutationObserver();
  92. }
  93.  
  94. main();
  95.  
  96. })();