GreenWall: View all contribution graphs in GitHub ⬜🟩

View a graph of users' contributions over the years in GitHub.

目前為 2024-05-08 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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.1.1
// @namespace          https://green-wall.leoku.dev
// @author             LeoKu(https://leoku.dev)
// @match              https://github.com/*
// @run-at             document-end
// @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));
};
var _a, _b, _c;
var isProfile = new RegExp(/^https:\/\/github\.com\/.+\/?$/).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 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);
        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');
        });
        dialog.addEventListener('click', function () {
            dialog.close();
        });
        // ---
        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();
            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[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_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 () {
                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.');
    }
}