一键查看历年 GitHub 的贡献图

在 GitHub 中查看用户历年的贡献图。

当前为 2024-04-21 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name 一键查看历年 GitHub 的贡献图
  3. // @namespace LeoKu(https://leoku.top)
  4. // @version 1.0.3
  5. // @description 在 GitHub 中查看用户历年的贡献图。
  6. // @author LeoKu
  7. // @match https://github.com/*
  8. // @run-at document-end
  9. // @icon https://green-wall.leoku.dev/favicon.svg
  10. // @grant GM.xmlHttpRequest
  11. // @homepageURL https://github.com/Codennnn/Green-Wall
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
  16. if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
  17. if (ar || !(i in from)) {
  18. if (!ar) ar = Array.prototype.slice.call(from, 0, i);
  19. ar[i] = from[i];
  20. }
  21. }
  22. return to.concat(ar || Array.prototype.slice.call(from));
  23. };
  24. var _a, _b, _c;
  25. var ORIGIN = 'https://green-wall.leoku.dev';
  26. function produceData(_a) {
  27. var data = _a.data;
  28. var contributionCalendars = data.contributionCalendars.map(function (cur) {
  29. var rows = [[], [], [], [], [], [], []];
  30. cur.weeks.forEach(function (_a) {
  31. var days = _a.days;
  32. if (days.length !== 7) {
  33. var newDays = __spreadArray([], days, true);
  34. for (var i = 0; i <= 6; i++) {
  35. var theDay = newDays.at(i);
  36. var weekday = i;
  37. if (theDay && typeof theDay.weekday === 'number') {
  38. if (theDay.weekday === weekday) {
  39. rows[theDay.weekday].push(theDay);
  40. }
  41. else {
  42. newDays.splice(i, 0, { level: 'Null', weekday: weekday });
  43. rows[i].push({ level: 'Null', weekday: weekday });
  44. }
  45. }
  46. else {
  47. rows[i].push({ level: 'Null', weekday: weekday });
  48. }
  49. }
  50. }
  51. else {
  52. days.forEach(function (day) {
  53. if (typeof day.weekday === 'number') {
  54. rows[day.weekday].push(day);
  55. }
  56. });
  57. }
  58. });
  59. var calendar = {
  60. total: cur.total,
  61. year: cur.year,
  62. rows: rows,
  63. };
  64. return calendar;
  65. });
  66. return {
  67. contributionCalendars: contributionCalendars,
  68. };
  69. }
  70. function createGraph(params) {
  71. var year = params.year, total = params.total, rows = params.rows;
  72. var table = document.createElement('table');
  73. table.classList.add('ContributionCalendar-grid');
  74. table.style.borderSpacing = '3px';
  75. table.style.overflow = 'hidden';
  76. table.style.position = 'relative';
  77. var tbody = document.createElement('tbody');
  78. var tr = document.createElement('tr');
  79. tr.style.height = '10px';
  80. rows.forEach(function (row) {
  81. var clonedTr = tr.cloneNode();
  82. var htmlStr = '';
  83. row.forEach(function (col, idx) {
  84. var td = '<td></td>';
  85. if (col.level !== "Null" /* ContributionLevel.Null */) {
  86. var level = col.level === "NONE" /* ContributionLevel.NONE */
  87. ? 0
  88. : col.level === "FIRST_QUARTILE" /* ContributionLevel.FIRST_QUARTILE */
  89. ? 1
  90. : col.level === "SECOND_QUARTILE" /* ContributionLevel.SECOND_QUARTILE */
  91. ? 2
  92. : col.level === "THIRD_QUARTILE" /* ContributionLevel.THIRD_QUARTILE */
  93. ? 3
  94. : 4;
  95. td = "\n <td\n tabindex=\"-1\"\n data-ix=\"".concat(idx, "\"\n style=\"width: 10px\"\n data-level=\"").concat(level, "\"\n role=\"gridcell\"\n class=\"ContributionCalendar-day\"\n ></td>\n ");
  96. }
  97. htmlStr += td;
  98. });
  99. if (clonedTr instanceof HTMLTableRowElement) {
  100. clonedTr.innerHTML = htmlStr;
  101. tbody.append(clonedTr);
  102. }
  103. });
  104. table.appendChild(tbody);
  105. var graphItem = document.createElement('div');
  106. var countText = document.createElement('div');
  107. countText.style.marginBottom = '5px';
  108. countText.textContent = "".concat(total, " contributions in ").concat(year);
  109. graphItem.append(countText, table);
  110. return { graphItem: graphItem };
  111. }
  112. function createDialog(params) {
  113. var username = params.username;
  114. var dialog = document.createElement('dialog');
  115. dialog.id = 'green-wall-dialog';
  116. dialog.classList.add('Overlay', 'Overlay-whenNarrow', 'Overlay--size-medium-portrait', 'Overlay--motion-scaleFadeOverlay', 'Overlay-whenNarrow', 'Overlay--size-medium-portrait', 'Overlay--motion-scaleFade');
  117. dialog.style.minWidth = '720px';
  118. dialog.style.maxHeight = 'calc(100vh - 50px)';
  119. var handleDialogClose = function () {
  120. dialog.close();
  121. document.body.classList.remove('has-modal');
  122. };
  123. dialog.addEventListener('click', function () {
  124. handleDialogClose();
  125. });
  126. // ---
  127. var wrap = document.createElement('div');
  128. wrap.style.display = 'flex';
  129. wrap.style.flexDirection = 'column';
  130. wrap.style.overflow = 'hidden';
  131. wrap.addEventListener('click', function (ev) {
  132. ev.stopPropagation();
  133. });
  134. // ---
  135. var dialogHeader = document.createElement('div');
  136. dialogHeader.classList.add('Overlay-header');
  137. var contentWrap = document.createElement('div');
  138. contentWrap.classList.add('Overlay-headerContentWrap');
  139. var titleWrap = document.createElement('div');
  140. titleWrap.classList.add('Overlay-titleWrap');
  141. var title = document.createElement('h1');
  142. title.classList.add('Overlay-title');
  143. title.textContent = "".concat(username, "'s GreenWall");
  144. var actionWrap = document.createElement('div');
  145. actionWrap.classList.add('Overlay-actionWrap');
  146. var actionButton = document.createElement('button');
  147. actionButton.classList.add('close-button', 'Overlay-closeButton');
  148. actionButton.setAttribute('type', 'button');
  149. actionButton.innerHTML = "\n <svg aria-hidden=\"true\" height=\"16\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" data-view-component=\"true\" class=\"octicon octicon-x\">\n <path d=\"M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z\"></path>\n </svg>\n ";
  150. actionButton.addEventListener('click', function (ev) {
  151. ev.stopPropagation();
  152. handleDialogClose();
  153. });
  154. // ---
  155. var dialogBody = document.createElement('div');
  156. dialogBody.classList.add('Overlay-body');
  157. dialogBody.style.overflowY = 'auto';
  158. var dialogContent = document.createElement('div');
  159. dialogContent.style.display = 'flex';
  160. dialogContent.style.flexDirection = 'column';
  161. dialogContent.style.rowGap = '10px';
  162. dialogContent.style.alignItems = 'center';
  163. dialogContent.style.padding = 'var(--stack-padding-normal, 1rem)';
  164. // ---
  165. var dialogFooter = document.createElement('div');
  166. dialogFooter.classList.add('Overlay-footer', 'Overlay-footer--alignEnd', 'Overlay-footer--divided');
  167. var openExtrnalBtn = document.createElement('button');
  168. var btnContent = document.createElement('span');
  169. btnContent.classList.add('Button-label');
  170. btnContent.textContent = 'Open in Green Wall';
  171. openExtrnalBtn.classList.add('Button', 'Button--primary', 'Button--medium');
  172. openExtrnalBtn.addEventListener('click', function () {
  173. window.open("".concat(ORIGIN, "/user/").concat(username), '_blank');
  174. });
  175. titleWrap.append(title);
  176. actionWrap.append(actionButton);
  177. contentWrap.append(titleWrap, actionWrap);
  178. openExtrnalBtn.append(btnContent);
  179. dialogHeader.append(contentWrap);
  180. dialogBody.append(dialogContent);
  181. dialogFooter.append(openExtrnalBtn);
  182. wrap.append(dialogHeader, dialogBody, dialogFooter);
  183. dialog.append(wrap);
  184. document.body.append(dialog);
  185. return { dialog: dialog, dialogContent: dialogContent };
  186. }
  187. var profileArea = document.querySelector('.Layout-sidebar .h-card .js-profile-editable-replace');
  188. var refNode = (_b = (_a = document.querySelector('.js-profile-editable-replace > .d-flex.flex-column')) === null || _a === void 0 ? void 0 : _a.nextSibling) === null || _b === void 0 ? void 0 : _b.nextSibling;
  189. if (profileArea instanceof HTMLElement && refNode instanceof HTMLElement) {
  190. var username_1 = (_c = document
  191. .querySelector('meta[property="profile:username"]')) === null || _c === void 0 ? void 0 : _c.getAttribute('content');
  192. if (username_1) {
  193. var block = document.createElement('div');
  194. block.classList.add('border-top', 'color-border-muted', 'pt-3', 'mt-3', 'clearfix', 'hide-sm', 'hide-md');
  195. var title = document.createElement('h2');
  196. title.classList.add('h4', 'mb-2');
  197. title.textContent = 'Green Wall';
  198. var openBtn = document.createElement('button');
  199. openBtn.classList.add('btn');
  200. openBtn.textContent = 'View All Green';
  201. block.appendChild(title);
  202. block.appendChild(openBtn);
  203. profileArea.insertBefore(block, refNode);
  204. var _d = createDialog({ username: username_1 }), dialog_1 = _d.dialog, dialogContent_1 = _d.dialogContent;
  205. var hasLoaded_1 = false;
  206. var handleLoadError_1 = function () {
  207. dialogContent_1.innerHTML = '';
  208. var errorBlock = document.createElement('div');
  209. errorBlock.style.display = 'flex';
  210. errorBlock.style.flexDirection = 'column';
  211. errorBlock.style.alignItems = 'center';
  212. var tip = document.createElement('p');
  213. tip.textContent = 'The process of obtaining data has an exception.';
  214. var retryBtn = document.createElement('button');
  215. retryBtn.classList.add('btn');
  216. retryBtn.textContent = 'Retry';
  217. retryBtn.addEventListener('click', function () {
  218. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  219. handleLoadData_1();
  220. });
  221. errorBlock.append(tip, retryBtn);
  222. dialogContent_1.append(errorBlock);
  223. };
  224. var handleLoadData_1 = function () {
  225. var loading = "\n <svg aria-label=\"Loading\" style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"32\" height=\"32\" viewBox=\"0 0 16 16\" fill=\"none\" data-view-component=\"true\" class=\"anim-rotate\">\n <circle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"></circle>\n <path d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"></path>\n </svg>\n ";
  226. dialogContent_1.innerHTML = loading;
  227. GM.xmlHttpRequest({
  228. method: 'GET',
  229. url: "".concat(ORIGIN, "/api/contribution/").concat(username_1),
  230. onload: function (response) {
  231. try {
  232. dialogContent_1.innerHTML = '';
  233. var data = JSON.parse(response.responseText);
  234. var xData = produceData(data);
  235. xData.contributionCalendars.forEach(function (calendar) {
  236. var graphItem = createGraph(calendar).graphItem;
  237. dialogContent_1.append(graphItem);
  238. });
  239. hasLoaded_1 = true;
  240. }
  241. catch (_a) {
  242. handleLoadError_1();
  243. }
  244. },
  245. onerror: function (err) {
  246. console.error('[Green Wall]: ', err);
  247. handleLoadError_1();
  248. },
  249. });
  250. };
  251. var handleDialogOpen_1 = function () {
  252. dialog_1.showModal();
  253. document.body.classList.add('has-modal');
  254. if (!hasLoaded_1) {
  255. handleLoadData_1();
  256. }
  257. };
  258. openBtn.addEventListener('click', function () {
  259. handleDialogOpen_1();
  260. });
  261. }
  262. }
  263. else {
  264. console.warn('[Green Wall]: Target node not found.');
  265. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址