TGithub

Adds some features on github.com & gitlab.com to integrate it with tecnativa.com

2026/01/27のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name           TGithub
// @author         Alexandre Díaz, Carlos Roca, Carlos Dauden
// @version        1.17.2
// @grant          none
// @run-at         document-idle
// @namespace      tecnativa
// @icon           https://www.tecnativa.com/web/image/website/1/favicon/
// @match          *://github.com/*
// @match          *://gitlab.tecnativa.com/*
// @description    Adds some features on github.com & gitlab.com to integrate it with tecnativa.com
// ==/UserScript==

(function (window) {
    "use strict";

    const TGithub = {
        ODOO_SERVER: 'https://www.tecnativa.com',
        COMPANY_NAME: 'Tecnativa',
        REGEX_TEMPLATES: {},

        init: function () {
            if (this.initialized) {
                return;
            }
            this.initialized = true;
            this._addRegexTemplate('TT', /\bTT(\d+)/gi, `<a target='_blank' href='${this.ODOO_SERVER}/web#id=$1&model=project.task&view_type=form'>${this.COMPANY_NAME}-Task #$1</a>`);
            this._replaceTask();
            if (this._isLocationHost('github')) {
                this._ensureGhNavbarButton();
                this._startGhNavbarKeepAlive();
            }
        },

        _isLocationHost: function (host) {
            return document.location.host.toLowerCase().includes(host);
        },

        _addRegexTemplate: function (templateName, regex, html) {
            this.REGEX_TEMPLATES[templateName] = { regex, html };
        },

        _executeRegexReplace: function (templateName, text) {
            if (templateName in this.REGEX_TEMPLATES && text.match(this.REGEX_TEMPLATES[templateName].regex)) {
                return text.replace(this.REGEX_TEMPLATES[templateName].regex, this.REGEX_TEMPLATES[templateName].html);
            }
            return false;
        },

        _replaceTask: function () {
            const searchAndParse = () => {
                document.querySelectorAll('.comment-body, .note-text, .description, .commit-description, .js-comment-body, .js-issue-title, .markdown-body').forEach((elm) => {
                    const htmlTemplate = this._executeRegexReplace('TT', elm.innerHTML);
                    if (htmlTemplate) {
                        elm.innerHTML = htmlTemplate;
                    }
                });
            };
            // Setting up MutationObserver to handle dynamic content updates
            if (typeof this.observer === 'undefined') {
                const targetNode = document.body;

                if (targetNode) {
                    this.observer = new MutationObserver((mutations) => {
                        mutations.forEach(() => searchAndParse());
                    });
                    this.observer.observe(targetNode, { childList: true, subtree: true });
                }
            }
            // Initial execution to replace already loaded content
            searchAndParse();
        },

        _findGhHeaderTarget: function () {
            return (
                document.querySelector('.AppHeader-globalBar-end') ||
                document.querySelector('div[data-testid="top-bar-actions"]')
            );
        },

        _ensureGhNavbarButton: function () {
            const targetNode = this._findGhHeaderTarget();
            if (!targetNode) return false;

            const existing = targetNode.querySelector('#pulls-tecnativa');
            if (existing && existing.isConnected) return true;
            document.querySelectorAll('#pulls-tecnativa').forEach((n) => n.remove());

            const menuItem = document.createElement('a');
            menuItem.id = 'pulls-tecnativa';
            menuItem.textContent = this.COMPANY_NAME;
            menuItem.href = `/pulls?q=is%3Aopen+is%3Apr+archived%3Afalse+involves%3A${this.COMPANY_NAME}`;

            const exampleItem =
                targetNode.querySelector("a[href$='/pulls']") ||
                targetNode.querySelector("a[href*='/pulls']") ||
                targetNode.querySelector('a');

            if (exampleItem) {
                menuItem.className = exampleItem.className;
                menuItem.style.cssText = (exampleItem.style.cssText || '') + 'width:auto;';
            } else {
                menuItem.style.cssText = 'margin-right:8px; display:inline-flex; align-items:center;';
            }

            targetNode.insertAdjacentElement('afterbegin', menuItem);
            return true;
        },

        _startGhNavbarKeepAlive: function () {
            if (this._keepAliveStarted) return;
            this._keepAliveStarted = true;

            const tick = () => this._ensureGhNavbarButton();
            tick();
            if (!this._navbarObserver) {
                this._navbarObserver = new MutationObserver(() => {
                    const target = this._findGhHeaderTarget();
                    if (target && !target.querySelector('#pulls-tecnativa')) {
                        this._ensureGhNavbarButton();
                    }
                });
                this._navbarObserver.observe(document.body, { childList: true, subtree: true });
            }
            if (!this._navbarInterval) {
                this._navbarInterval = setInterval(() => {
                    const target = this._findGhHeaderTarget();
                    if (!target) return;
                    if (!target.querySelector('#pulls-tecnativa')) this._ensureGhNavbarButton();
                }, 2000);
            }
            const burst = () => {
                let i = 0;
                const run = () => {
                    i += 1;
                    tick();
                    if (i < 10) requestAnimationFrame(run);
                };
                requestAnimationFrame(run);
            };

            window.addEventListener('popstate', burst, true);
            this._onNavBurst = burst;
        },

        // Override history methods to detect navigation
        overrideHistoryMethods: function () {
            const originalPushState = history.pushState;
            const originalReplaceState = history.replaceState;
            const handleStateChange = () => {
                this.initialized = false; // Allow reinitialization
                this.init();
            };
            history.pushState = (...args) => {
                originalPushState.apply(history, args);
                handleStateChange();
            };
            history.replaceState = (...args) => {
                originalReplaceState.apply(history, args);
                handleStateChange();
            };
            window.addEventListener('popstate', handleStateChange);
        },
    };

    // Initialize the script when the DOM is ready
    const ready = (callback) => {
        if (document.readyState !== 'loading') {
            callback();
        } else {
            document.addEventListener('DOMContentLoaded', callback);
        }
    };

    ready(() => {
        TGithub.init();
        TGithub.overrideHistoryMethods();
    });

})(window);