GithubDashboardEnhance

Show the latest 30 starred repos in new Github dashboard and shortcuts in header navigation.

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 or Violentmonkey 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                GithubDashboardEnhance
// @description         Show the latest 30 starred repos in new Github dashboard and shortcuts in header navigation.
// @description:zh-CN   在 Github 新首页显示最近 30 个 star 项目,在头部导航栏中显示快捷方式
// @description:zh-TW   在 Github 新首頁顯示最近 30 個 star 項目,在頭部導航欄中顯示快捷方式
// @author              ladit
// @version             1.1.2
// @namespace           https://greasyfork.org/zh-CN/scripts/33511
// @homepageURL         https://github.com/ladit/Userscripts
// @supportURL          https://github.com/ladit/Userscripts

// @grant               GM.setValue
// @grant               GM.getValue
// @grant               GM.xmlHttpRequest
// @run-at              document-idle
// @include             https://github.com/*
// @connect             api.github.com
// ==/UserScript==

(async () => {
    let userName = document.querySelector('meta[name="user-login"]').getAttribute('content')
    if (userName === '') {
        return;
    }
    const actionMenu = document.querySelector('.AppHeader-actions > action-menu')
    if (actionMenu) {
        actionMenu.insertAdjacentHTML('afterend', `<div data-view-component="true" class="Button-withTooltip"> <a href="https://github.com/${userName}" id="item-profile-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-profile-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-person"> <path d="M10.561 8.073a6.005 6.005 0 0 1 3.432 5.142.75.75 0 1 1-1.498.07 4.5 4.5 0 0 0-8.99 0 .75.75 0 0 1-1.498-.07 6.004 6.004 0 0 1 3.431-5.142 3.999 3.999 0 1 1 5.123 0ZM10.5 5a2.5 2.5 0 1 0-5 0 2.5 2.5 0 0 0 5 0Z"> </path> </svg> </a> <tool-tip id="tooltip-profile-button" for="item-profile-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Profile</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="https://github.com/${userName}?tab=repositories&type=source" id="item-repositories-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-repositories-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-repo"> <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"> </path> </svg> </a> <tool-tip id="tooltip-repositories-button" for="item-repositories-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Repositories</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="/${userName}?tab=projects" id="item-projects-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-projects-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-project"> <path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H1.75a.25.25 0 0 0-.25.25ZM11.75 3a.75.75 0 0 1 .75.75v7.5a.75.75 0 0 1-1.5 0v-7.5a.75.75 0 0 1 .75-.75Zm-8.25.75a.75.75 0 0 1 1.5 0v5.5a.75.75 0 0 1-1.5 0ZM8 3a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 3Z"> </path> </svg> </a> <tool-tip id="tooltip-projects-button" for="item-projects-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Projects</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="/codespaces" id="item-codespaces-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-codespaces-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-codespaces"> <path d="M0 11.25c0-.966.784-1.75 1.75-1.75h12.5c.966 0 1.75.784 1.75 1.75v3A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm2-9.5C2 .784 2.784 0 3.75 0h8.5C13.216 0 14 .784 14 1.75v5a1.75 1.75 0 0 1-1.75 1.75h-8.5A1.75 1.75 0 0 1 2 6.75Zm1.75-.25a.25.25 0 0 0-.25.25v5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5a.25.25 0 0 0-.25-.25Zm-2 9.5a.25.25 0 0 0-.25.25v3c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-3a.25.25 0 0 0-.25-.25Z"> </path> <path d="M7 12.75a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Zm-4 0a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1-.75-.75Z"> </path> </svg> </a> <tool-tip id="tooltip-codespaces-button" for="item-codespaces-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Codespaces</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="/settings/organizations" id="item-organizations-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-organizations-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-organization"> <path d="M1.75 16A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0h8.5C11.216 0 12 .784 12 1.75v12.5c0 .085-.006.168-.018.25h2.268a.25.25 0 0 0 .25-.25V8.285a.25.25 0 0 0-.111-.208l-1.055-.703a.749.749 0 1 1 .832-1.248l1.055.703c.487.325.779.871.779 1.456v5.965A1.75 1.75 0 0 1 14.25 16h-3.5a.766.766 0 0 1-.197-.026c-.099.017-.2.026-.303.026h-3a.75.75 0 0 1-.75-.75V14h-1v1.25a.75.75 0 0 1-.75.75Zm-.25-1.75c0 .138.112.25.25.25H4v-1.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 .75.75v1.25h2.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25ZM3.75 6h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM3 3.75A.75.75 0 0 1 3.75 3h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 3 3.75Zm4 3A.75.75 0 0 1 7.75 6h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 7 6.75ZM7.75 3h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5ZM3 9.75A.75.75 0 0 1 3.75 9h.5a.75.75 0 0 1 0 1.5h-.5A.75.75 0 0 1 3 9.75ZM7.75 9h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1 0-1.5Z"> </path> </svg> </a> <tool-tip id="tooltip-organizations-button" for="item-organizations-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Organizations</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="/${userName}?tab=stars" id="item-stars-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-stars-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-star"> <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"> </path> </svg> </a> <tool-tip id="tooltip-stars-button" for="item-stars-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Stars</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="/sponsors/accounts" id="item-sponsors-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-sponsors-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-heart"> <path d="m8 14.25.345.666a.75.75 0 0 1-.69 0l-.008-.004-.018-.01a7.152 7.152 0 0 1-.31-.17 22.055 22.055 0 0 1-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.066 22.066 0 0 1-3.744 2.584l-.018.01-.006.003h-.002ZM4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.58 20.58 0 0 0 8 13.393a20.58 20.58 0 0 0 3.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.749.749 0 0 1-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5Z"> </path> </svg> </a> <tool-tip id="tooltip-sponsors-button" for="item-sponsors-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Sponsors</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="https://gist.github.com/mine" id="item-gist-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-gist-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-code-square"> <path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25Zm7.47 3.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L10.69 8 9.22 6.53a.75.75 0 0 1 0-1.06ZM6.78 6.53 5.31 8l1.47 1.47a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"> </path> </svg> </a> <tool-tip id="tooltip-gist-button" for="item-gist-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Gists</tool-tip></div><div data-view-component="true" class="Button-withTooltip"> <a href="https://docs.github.com" id="item-docs-button" data-view-component="true" class="Button Button--iconOnly Button--secondary Button--medium AppHeader-button color-fg-muted rgh-seen--7567559453" aria-labelledby="tooltip-docs-button"> <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-book"> <path d="M0 1.75A.75.75 0 0 1 .75 1h4.253c1.227 0 2.317.59 3 1.501A3.743 3.743 0 0 1 11.006 1h4.245a.75.75 0 0 1 .75.75v10.5a.75.75 0 0 1-.75.75h-4.507a2.25 2.25 0 0 0-1.591.659l-.622.621a.75.75 0 0 1-1.06 0l-.622-.621A2.25 2.25 0 0 0 5.258 13H.75a.75.75 0 0 1-.75-.75Zm7.251 10.324.004-5.073-.002-2.253A2.25 2.25 0 0 0 5.003 2.5H1.5v9h3.757a3.75 3.75 0 0 1 1.994.574ZM8.755 4.75l-.004 7.322a3.752 3.752 0 0 1 1.992-.572H14.5v-9h-3.495a2.25 2.25 0 0 0-2.25 2.25Z"> </path> </svg> </a> <tool-tip id="tooltip-docs-button" for="item-docs-button" data-direction="s" data-type="label" data-view-component="true" class="position-absolute sr-only" aria-hidden="true" role="tooltip">Docs</tool-tip></div>`)
    }

    const rightFooter = document.querySelector('div[aria-label="Explore repositories"] > .footer')
    if (!rightFooter) {
        return;
    }

    const fetchMap = async (key, defaultMap) => {
        const v = await GM.getValue(key)
        if (!v) {
            return defaultMap
        }
        return new Map(JSON.parse(v))
    }

    const storeMap = async (key, m) => {
        await GM.setValue(key, JSON.stringify(Array.from(m.entries())))
    }

    const lastStoreColors = await GM.getValue('lastStoreColors', 0)
    if (lastStoreColors + 30 * 86400000 < Date.now()) {
        GM.xmlHttpRequest({
            method: 'GET',
            timeout: 5000,
            responseType: 'json',
            url: 'https://raw.githubusercontent.com/ozh/github-colors/master/colors.json',
            onload: async resp => {
                let languageColors = new Map()
                for (const [language, v] of Object.entries(resp.response)) {
                    languageColors.set(language, v.color)
                }
                await storeMap('languageColors', languageColors)
                await GM.setValue('lastStoreColors', Date.now())
            },
            onerror: resp => {
                console.log('[GithubDashboardEnhance]: request colors failed: ', resp)
            },
            ontimeout: () => {
                console.log('[GithubDashboardEnhance]: Request colors timeout.')
            },
        })
    }

    const lastStoreStarredReposTime = await GM.getValue('lastStoreStarredReposTime', 0)
    if (lastStoreStarredReposTime + 86400000 < Date.now()) {
        GM.xmlHttpRequest({
            method: 'GET',
            timeout: 5000,
            responseType: 'json',
            url: `https://api.github.com/users/${userName}/starred`,
            onload: async resp => {
                const languageColors = await fetchMap('languageColors', new Map())
                let starredReposBlock = '<h2 class="f5 text-bold pt-3 mt-4 border-top">Recent Starred repositories</h2><div data-view-component="true">'
                let i = 0
                for (const repo of resp.response) {
                    i += 1
                    let border = ''
                    if (i < resp.response.length) {
                        border = 'border-bottom'
                    }
                    starredReposBlock += `<div data-view-component="true" class="py-4 ${border}"> <div data-view-component="true" class="Truncate d-flex flex-justify-between"> <span style="word-wrap:normal;max-width: 300px;" data-view-component="true" class="Truncate-text ws-normal flex-1"> <img src="${repo.owner.avatar_url}" alt="@${repo.owner.login} profile" size="20" height="20" width="20" data-view-component="true" class="avatar avatar-small circle box-shadow-none mr-1"> <a href="${repo.html_url}" data-view-component="true" class="color-fg-default text-bold"> ${repo.owner.login} <span data-view-component="true" class="color-fg-muted text-light">/</span> ${repo.name} </a></span> </div> <p data-view-component="true" class="text-small color-fg-muted mt-2"> ${repo.description} </p> <div data-view-component="true" class="f6 color-fg-muted d-inline-block mr-4 mt-1"> <svg aria-label="star" role="img" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-star"> <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z"> </path> </svg> ${repo.stargazers_count > 1000 ? (repo.stargazers_count / 1000).toFixed(1) + 'k' : repo.stargazers_count} </div> <div data-view-component="true" class="f6 color-fg-muted d-inline-block mt-1"> <span class=""> <span class="repo-language-color" style="background-color: ${languageColors.get(repo.language)}"></span> <span itemprop="programmingLanguage">${repo.language}</span> </span> </div> </div>`
                }
                starredReposBlock += `<a href="/${userName}?tab=stars" data-view-component="true">More →</a></div>`
                await GM.setValue('starredReposBlock', starredReposBlock)
                await GM.setValue('lastStoreStarredReposTime', Date.now())
            },
            onerror: resp => {
                console.log('[GithubDashboardEnhance]: request failed: ', resp)
            },
            ontimeout: () => {
                console.log('[GithubDashboardEnhance]: Request timeout.')
            },
        })
    }
    rightFooter.insertAdjacentHTML('beforebegin', await GM.getValue('starredReposBlock', ''))
})()