一键查看历年 GitHub 的贡献图

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

目前为 2024-04-19 提交的版本。查看 最新版本

// ==UserScript==
// @name         一键查看历年 GitHub 的贡献图
// @namespace    LeoKu(https://leoku.top)
// @version      1.0.0
// @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, "/share/").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 = '获取数据的过程出现异常。';
            var retryBtn = document.createElement('button');
            retryBtn.classList.add('btn');
            retryBtn.textContent = '点击重试';
            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]: 未找到目标节点。');
}