一键查看历年 GitHub 的贡献图

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

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         一键查看历年 GitHub 的贡献图
// @namespace    LeoKu(https://leoku.top)
// @version      1.0.2
// @description  在 GitHub 中查看用户历年的贡献图。
// @author       LeoKu
// @match        https://github.com/*
// @run-at       document-end
// @grant        GM.xmlHttpRequest
// @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));
};
var _a, _b, _c;
var ORIGIN = 'https://green-wall.leoku.dev';
function produceData(_a) {
    var data = _a.data;
    var contributionCalendars = data.contributionCalendars.map(function (cur) {
        var rows = [[], [], [], [], [], [], []];
        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, { level: 'Null', weekday: weekday });
                            rows[i].push({ level: 'Null', weekday: weekday });
                        }
                    }
                    else {
                        rows[i].push({ level: 'Null', weekday: weekday });
                    }
                }
            }
            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,
    };
}
function createGraph(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          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        ");
            }
            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);
    return { graphItem: graphItem };
}
function createDialog(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)';
    var handleDialogClose = function () {
        dialog.close();
        document.body.classList.remove('has-modal');
    };
    dialog.addEventListener('click', function () {
        handleDialogClose();
    });
    // ---
    var wrap = document.createElement('div');
    wrap.style.display = 'flex';
    wrap.style.flexDirection = 'column';
    wrap.style.overflow = 'hidden';
    wrap.addEventListener('click', function (ev) {
        ev.stopPropagation();
    });
    // ---
    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();
        handleDialogClose();
    });
    // ---
    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, "/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[property="profile:username"]')) === null || _c === void 0 ? void 0 : _c.getAttribute('content');
    if (username_1) {
        var block = document.createElement('div');
        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, "/api/contribution/").concat(username_1),
                onload: function (response) {
                    try {
                        dialogContent_1.innerHTML = '';
                        var data = JSON.parse(response.responseText);
                        var xData = produceData(data);
                        xData.contributionCalendars.forEach(function (calendar) {
                            var graphItem = createGraph(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 () {
            dialog_1.showModal();
            document.body.classList.add('has-modal');
            if (!hasLoaded_1) {
                handleLoadData_1();
            }
        };
        openBtn.addEventListener('click', function () {
            handleDialogOpen_1();
        });
    }
}
else {
    console.warn('[Green Wall]: Target node not found.');
}