GreenWall: View all contribution graphs in GitHub ⬜🟩

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

As of 08.05.2024. See апошняя версія.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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.');
    }
}