您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 GitHub 中查看用户历年的贡献图。
// ==UserScript== // @name GreenWall: View all contribution graphs in GitHub ⬜🟩 // @description View a graph of users' contributions over the years in GitHub. // @name:zh-CN GreenWall - 查看历年 GitHub 的贡献图 ⬜🟩 // @description:zh-CN 在 GitHub 中查看用户历年的贡献图。 // @version 1.2.0 // @namespace https://green-wall.leoku.dev // @author LeoKu(https://leoku.dev) // @match https://github.com/* // @run-at document-start // @icon https://green-wall.leoku.dev/favicon.svg // @grant GM.xmlHttpRequest // @homepageURL https://github.com/Codennnn/Green-Wall // @license MIT // ==/UserScript== var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; function addHistoryEvent(type) { var originalMethod = window.history[type]; return function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } originalMethod.apply(window.history, args); var ev = new Event(type); window.dispatchEvent(ev); }; } window.history.replaceState = addHistoryEvent('replaceState'); var handler = function () { var _a, _b, _c; var githubUserPageRegex = /^https:\/\/github\.com\/[a-zA-Z0-9-]+(?=\/?$)/; var isProfile = githubUserPageRegex.test(window.location.href); if (isProfile) { var ORIGIN_1 = 'https://green-wall.leoku.dev'; var produceData_1 = function (_a) { var data = _a.data; var contributionCalendars = data.contributionCalendars.map(function (cur) { var rows = [[], [], [], [], [], [], []]; var nullDay = { count: 0, date: '', level: 'Null' }; cur.weeks.forEach(function (_a) { var days = _a.days; if (days.length !== 7) { var newDays = __spreadArray([], days, true); for (var i = 0; i <= 6; i++) { var theDay = newDays.at(i); var weekday = i; if (theDay && typeof theDay.weekday === 'number') { if (theDay.weekday === weekday) { rows[theDay.weekday].push(theDay); } else { newDays.splice(i, 0, nullDay); rows[i].push(nullDay); } } else { rows[i].push(nullDay); } } } else { days.forEach(function (day) { if (typeof day.weekday === 'number') { rows[day.weekday].push(day); } }); } }); var calendar = { total: cur.total, year: cur.year, rows: rows, }; return calendar; }); return { contributionCalendars: contributionCalendars, }; }; var isHalloween_1 = false; var createGraph_1 = function (params) { var year = params.year, total = params.total, rows = params.rows; var table = document.createElement('table'); table.classList.add('ContributionCalendar-grid'); table.style.borderSpacing = '3px'; table.style.overflow = 'hidden'; table.style.position = 'relative'; var tbody = document.createElement('tbody'); var tr = document.createElement('tr'); tr.style.height = '10px'; rows.forEach(function (row) { var clonedTr = tr.cloneNode(); var htmlStr = ''; row.forEach(function (col, idx) { var td = '<td></td>'; if (col.level !== "Null" /* ContributionLevel.Null */) { var level = col.level === "NONE" /* ContributionLevel.NONE */ ? 0 : col.level === "FIRST_QUARTILE" /* ContributionLevel.FIRST_QUARTILE */ ? 1 : col.level === "SECOND_QUARTILE" /* ContributionLevel.SECOND_QUARTILE */ ? 2 : col.level === "THIRD_QUARTILE" /* ContributionLevel.THIRD_QUARTILE */ ? 3 : 4; td = "\n <td\n title=\"".concat(col.count === 0 ? 'No' : col.count, " contributions in ").concat(col.date, "\"\n tabindex=\"-1\"\n data-ix=\"").concat(idx, "\"\n style=\"width: 10px\"\n data-level=\"").concat(level, "\"\n class=\"ContributionCalendar-day\"\n data-date=\"").concat(col.level, "\"\n aria-selected=\"false\"\n role=\"gridcell\"\n ></td>\n "); } htmlStr += td; }); if (clonedTr instanceof HTMLTableRowElement) { clonedTr.innerHTML = htmlStr; tbody.append(clonedTr); } }); table.appendChild(tbody); var graphItem = document.createElement('div'); var countText = document.createElement('div'); countText.style.marginBottom = '5px'; countText.textContent = "".concat(total, " contributions in ").concat(year); graphItem.append(countText, table); if (isHalloween_1) { graphItem.classList.add('ContributionCalendar'); graphItem.setAttribute('data-holiday', 'halloween'); } return { graphItem: graphItem }; }; var createDialog = function (params) { var username = params.username; var dialog = document.createElement('dialog'); dialog.id = 'green-wall-dialog'; dialog.classList.add('Overlay', 'Overlay-whenNarrow', 'Overlay--size-medium-portrait', 'Overlay--motion-scaleFadeOverlay', 'Overlay-whenNarrow', 'Overlay--size-medium-portrait', 'Overlay--motion-scaleFade'); dialog.style.minWidth = '720px'; dialog.style.maxHeight = 'calc(100vh - 50px)'; dialog.addEventListener('close', function () { document.body.classList.remove('has-modal'); }); var mouseDownTarget; var mouseDownHandler = function (ev) { if (ev.target instanceof HTMLElement) { mouseDownTarget = ev.target; } }; var mouseUpHandler = function (ev) { if (ev.target instanceof HTMLDialogElement && ev.target === mouseDownTarget && ev.target === dialog) { dialog.close(); } }; dialog.addEventListener('mousedown', mouseDownHandler); dialog.addEventListener('mouseup', mouseUpHandler); // --- var wrap = document.createElement('div'); wrap.style.display = 'flex'; wrap.style.flexDirection = 'column'; wrap.style.overflow = 'hidden'; // --- var dialogHeader = document.createElement('div'); dialogHeader.classList.add('Overlay-header'); var contentWrap = document.createElement('div'); contentWrap.classList.add('Overlay-headerContentWrap'); var titleWrap = document.createElement('div'); titleWrap.classList.add('Overlay-titleWrap'); var title = document.createElement('h1'); title.classList.add('Overlay-title'); title.textContent = "".concat(username, "'s GreenWall"); var actionWrap = document.createElement('div'); actionWrap.classList.add('Overlay-actionWrap'); var actionButton = document.createElement('button'); actionButton.classList.add('close-button', 'Overlay-closeButton'); actionButton.setAttribute('type', 'button'); 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 "; actionButton.addEventListener('click', function (ev) { ev.stopPropagation(); dialog.close(); }); // --- var dialogBody = document.createElement('div'); dialogBody.classList.add('Overlay-body'); dialogBody.style.overflowY = 'auto'; var dialogContent = document.createElement('div'); dialogContent.style.display = 'flex'; dialogContent.style.flexDirection = 'column'; dialogContent.style.rowGap = '10px'; dialogContent.style.alignItems = 'center'; dialogContent.style.padding = 'var(--stack-padding-normal, 1rem)'; // --- var dialogFooter = document.createElement('div'); dialogFooter.classList.add('Overlay-footer', 'Overlay-footer--alignEnd', 'Overlay-footer--divided'); var openExtrnalBtn = document.createElement('button'); var btnContent = document.createElement('span'); btnContent.classList.add('Button-label'); btnContent.textContent = 'Open in Green Wall'; openExtrnalBtn.classList.add('Button', 'Button--primary', 'Button--medium'); openExtrnalBtn.addEventListener('click', function () { window.open("".concat(ORIGIN_1, "/user/").concat(username), '_blank'); }); titleWrap.append(title); actionWrap.append(actionButton); contentWrap.append(titleWrap, actionWrap); openExtrnalBtn.append(btnContent); dialogHeader.append(contentWrap); dialogBody.append(dialogContent); dialogFooter.append(openExtrnalBtn); wrap.append(dialogHeader, dialogBody, dialogFooter); dialog.append(wrap); document.body.append(dialog); return { dialog: dialog, dialogContent: dialogContent }; }; var profileArea = document.querySelector('.Layout-sidebar .h-card .js-profile-editable-replace'); 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; if (profileArea instanceof HTMLElement && refNode instanceof HTMLElement) { var username_1 = (_c = document .querySelector('meta[name="octolytics-dimension-user_login"]')) === null || _c === void 0 ? void 0 : _c.getAttribute('content'); if (username_1) { var exists = !!document.querySelector('#green-wall-block'); if (!exists) { var block = document.createElement('div'); block.setAttribute('id', 'green-wall-block'); block.classList.add('border-top', 'color-border-muted', 'pt-3', 'mt-3', 'clearfix', 'hide-sm', 'hide-md'); var title = document.createElement('h2'); title.classList.add('h4', 'mb-2'); title.textContent = 'Green Wall'; var openBtn = document.createElement('button'); openBtn.classList.add('btn'); openBtn.textContent = ' ⬜🟩 View All Green'; block.appendChild(title); block.appendChild(openBtn); profileArea.insertBefore(block, refNode); var _d = createDialog({ username: username_1 }), dialog_1 = _d.dialog, dialogContent_1 = _d.dialogContent; var hasLoaded_1 = false; var handleLoadError_1 = function () { dialogContent_1.innerHTML = ''; var errorBlock = document.createElement('div'); errorBlock.style.display = 'flex'; errorBlock.style.flexDirection = 'column'; errorBlock.style.alignItems = 'center'; var tip = document.createElement('p'); tip.textContent = 'The process of obtaining data has an exception.'; var retryBtn = document.createElement('button'); retryBtn.classList.add('btn'); retryBtn.textContent = 'Retry'; retryBtn.addEventListener('click', function () { // eslint-disable-next-line @typescript-eslint/no-use-before-define handleLoadData_1(); }); errorBlock.append(tip, retryBtn); dialogContent_1.append(errorBlock); }; var handleLoadData_1 = function () { 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 "; dialogContent_1.innerHTML = loading; GM.xmlHttpRequest({ method: 'GET', url: "".concat(ORIGIN_1, "/api/contribution/").concat(username_1), onload: function (response) { try { dialogContent_1.innerHTML = ''; var data = JSON.parse(response.responseText); var xData = produceData_1(data); xData.contributionCalendars.forEach(function (calendar) { var graphItem = createGraph_1(calendar).graphItem; dialogContent_1.append(graphItem); }); hasLoaded_1 = true; } catch (_a) { handleLoadError_1(); } }, onerror: function (err) { console.error('[Green Wall]: ', err); handleLoadError_1(); }, }); }; var handleDialogOpen_1 = function () { var _a; dialog_1.showModal(); document.body.classList.add('has-modal'); if (!hasLoaded_1) { isHalloween_1 = ((_a = document.querySelector('.ContributionCalendar')) === null || _a === void 0 ? void 0 : _a.getAttribute('data-holiday')) === 'halloween'; handleLoadData_1(); } }; openBtn.addEventListener('click', function () { handleDialogOpen_1(); }); } } } else { console.warn('[Green Wall]: Target node not found.'); } } }; window.addEventListener('replaceState', handler);