GitHub Useful Forks

Adds a button to GitHub repositories to see useful forks of the repo.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         GitHub Useful Forks
// @namespace    Violentmonkey Scripts
// @version      1.1
// @author       bethro
// @license      MIT
// @icon         https://useful-forks.github.io/assets/useful-forks-logo.png
// @description  Adds a button to GitHub repositories to see useful forks of the repo.
// @homepage     https://github.com/bethropolis/useful-forks
// @homepageURL  https://github.com/bethropolis/useful-forks
// @supportURL   https://github.com/bethropolis/useful-forks/issues
// @match        https://github.com/*/*
// @grant        none
// ==/UserScript==



(function() {
    'use strict';

    const UF_LI_ID  = "useful_forks_li";
    const UF_BTN_ID = "useful_forks_btn";
    const UF_TIP_ID = "useful_forks_tooltip";

    function getRepoUrl() {
        const pathComponents = window.location.pathname.split("/");
        const user = pathComponents[1], repo = pathComponents[2];
        return `https://useful-forks.github.io/?repo=${user}/${repo}`;
    }

    function setBtnUrl() {
        const button = document.getElementById(UF_BTN_ID);
        button.addEventListener("click", () => {
            window.open(getRepoUrl(), "_blank");
        });
    }

    function createUsefulBtn() {
        const li = document.createElement("li");
        const content = `
        <div class="float-left">
          <button id="${UF_BTN_ID}" class="btn-sm btn" aria-describedby="${UF_TIP_ID}">
            <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-search">
                <path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path>
            </svg>
            Useful
          </button>
          <tool-tip for="${UF_BTN_ID}" id="${UF_TIP_ID}" popover="manual" class="position-absolute sr-only">
            Search for useful forks in a new tab
          </tool-tip>
        </div>
        `;
        li.innerHTML = content;
        li.id = UF_LI_ID;
        return li;
    }

    function init() {
        // This is required for some cases like on Back/Forward navigation
        const oldLi = document.getElementById(UF_LI_ID);
        if (oldLi) {
            oldLi.remove();
        }

        const forkBtn = document.getElementById("repo-network-counter");
        if (forkBtn) { // sufficient to know the user is looking at a repository
            const forksAmount = forkBtn.textContent;
            if (forksAmount < 1) {
                return;
            }
            const parentLi = forkBtn.closest("li");
            const newLi = createUsefulBtn();
            parentLi.parentNode.insertBefore(newLi, parentLi);
            setBtnUrl(); // this needs to happen after the btn is inserted in the DOM
        }
    }

    init(); // entry point of the script

    /*
    Without a timeout, the Back/Forward browser navigation ends up messing up
    the JavaScript onClick event assigned to the button.
    The trade-off here is that the button appears a bit after the page loads.

    Moreover, it's worth pointing out that a MutationObserver is required here
    because GitHub does some optimizations that do not require reloading an
    entire page.
    PJax is one of those tricks used, but it does not seem to be exclusive,
    hence why `document.addEventListener("pjax:end", init);` is not sufficient.
    */
    let timeout;
    const observer = new MutationObserver(() => {
        clearTimeout(timeout);
        timeout = setTimeout(init, 10);
    });
    /*
    `subtree: false` is used to reduce the amount of callbacks triggered.
    `document.body` may be of narrower scope, but I couldn't figure it out.
    */
    observer.observe(document.body, { childList: true, subtree: false });

})();