Greasy Fork

Greasy Fork is available in English.

GitHub 增强工具栏

在 Github 网站顶部显示 Github.dev 和 DeepWiki 和 ZreadAi 按钮,方便更好地查看代码。当按钮过多时自动切换为图标模式。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         GitHub 增强工具栏
// @namespace    https://github.com/txy-sky
// @icon         https://github.com/favicons/favicon.svg
// @version      1.5.0
// @description  在 Github 网站顶部显示 Github.dev 和 DeepWiki 和 ZreadAi 按钮,方便更好地查看代码。当按钮过多时自动切换为图标模式。
// @author       Txy-Sky
// @match        https://github.com/*
// @run-at       document-end
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // 添加全局样式以修复按钮样式
    const style = `
        .custom-github-button {
            margin: 0 4px;
            display: flex;
            align-items: center;
            height: 28px;
        }
        .custom-github-button .octicon {
            margin-right: 4px;
            vertical-align: text-bottom;
        }
        .custom-github-button.icon-only .octicon {
            margin-right: 0;
        }
        .pagehead-actions > li {
            margin-right: 8px;
        }
    `;

    // 注入样式到页面
    const styleElement = document.createElement('style');
    styleElement.textContent = style;
    document.head.appendChild(styleElement);

    // 等待元素出现的函数
    function waitForElement(selector, timeout = 10000) {
        return new Promise((resolve, reject) => {
            const element = document.querySelector(selector);
            if (element) {
                resolve(element);
                return;
            }

            const observer = new MutationObserver((mutations, obs) => {
                const element = document.querySelector(selector);
                if (element) {
                    obs.disconnect();
                    resolve(element);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            // 超时处理
            setTimeout(() => {
                observer.disconnect();
                reject(new Error(`Element ${selector} not found within ${timeout}ms`));
            }, timeout);
        });
    }

    // 查找按钮容器的函数,提高兼容性
    async function findButtonContainer() {
        // 尝试多个可能的选择器
        const selectors = [
            'ul.pagehead-actions',
            '.pagehead-actions',
            '.file-navigation .d-flex',
            'nav[aria-label="Repository"] .d-flex'
        ];

        for (const selector of selectors) {
            const element = document.querySelector(selector);
            if (element) return element;
        }

        return null;
    }

    // 检测现有按钮数量的函数
    function countExistingButtons(container) {
        if (!container) return 0;
        
        // 计算现有按钮数量,排除我们即将添加的按钮
        const existingButtons = container.querySelectorAll('li:not(#githubdevButton):not(#zreadaiButton):not(#deepwikiButton)');
        return existingButtons.length;
    }

    // 统一按钮创建函数,支持仅图标模式
    function createCustomButton(id, url, iconHtml, text, iconOnly = false) {
        const li = document.createElement('li');
        li.id = id;
        li.className = 'd-flex';
        li.style.marginRight = '8px';

        const a = document.createElement('a');
        a.href = url;
        a.className = iconOnly ? 'btn btn-sm custom-github-button icon-only' : 'btn btn-sm custom-github-button';
        a.target = '_blank';
        a.rel = 'noopener noreferrer';
        
        if (iconOnly) {
            a.innerHTML = iconHtml;
            a.title = text; // 添加 tooltip 显示完整文本
        } else {
            a.innerHTML = `${iconHtml}<span>${text}</span>`;
        }

        li.appendChild(a);
        return li;
    }

    // 创建 Github.dev 按钮
    function createGithubDevButton(iconOnly = false) {
        const githubdevUrl = `https://github.dev${location.pathname}${location.search}${location.hash}`;
        const iconHtml = `<img class="octicon" width="16" height="16" src="https://github.com/favicons/favicon-codespaces.svg" />`;
        return createCustomButton('githubdevButton', githubdevUrl, iconHtml, 'Github.dev', iconOnly);
    }

    // 创建 ZreadAi 按钮
    function createZreadAiButton(iconOnly = false) {
        const zreadAiUrl = `https://zread.ai${location.pathname}${location.search}${location.hash}`;
        const iconHtml = `<svg aria-hidden="true" viewBox="0 0 32 32" version="1.1" width="16" height="16" class="octicon">
            <path d="M9.91922 3.2002H4.47922C3.77229 3.2002 3.19922 3.77327 3.19922 4.4802V9.9202C3.19922 10.6271 3.77229 11.2002 4.47922 11.2002H9.91922C10.6261 11.2002 11.1992 10.6271 11.1992 9.9202V4.4802C11.1992 3.77327 10.6261 3.2002 9.91922 3.2002Z" fill="currentColor"></path>
            <path d="M9.91922 20.7998H4.47922C3.77229 20.7998 3.19922 21.3729 3.19922 22.0798V27.5198C3.19922 28.2267 3.77229 28.7998 4.47922 28.7998H9.91922C10.6261 28.7998 11.1992 28.2267 11.1992 27.5198V22.0798C11.1992 21.3729 10.6261 20.7998 9.91922 20.7998Z" fill="currentColor"></path>
            <path d="M27.5208 3.2002H22.0808C21.3739 3.2002 20.8008 3.77327 20.8008 4.4802V9.9202C20.8008 10.6271 21.3739 11.2002 22.0808 11.2002H27.5208C28.2277 11.2002 28.8008 10.6271 28.8008 9.9202V4.4802C28.8008 3.77327 28.2277 3.2002 27.5208 3.2002Z" fill="currentColor"></path>
            <path d="M8 24L24 8L8 24Z" fill="currentColor"></path>
            <path d="M8 24L24 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"></path>
        </svg>`;
        return createCustomButton('zreadaiButton', zreadAiUrl, iconHtml, 'ZreadAi', iconOnly);
    }

    // 创建 DeepWiki 按钮
    function createDeepWikiButton(iconOnly = false) {
        const deepwikiUrl = `https://deepwiki.com${location.pathname}${location.search}${location.hash}`;
        const iconHtml = `<svg class="octicon" xmlns="http://www.w3.org/2000/svg" viewBox="110 110 460 500" width="16" height="16">
            <path d="M418.73,332.37c9.84-5.68,22.07-5.68,31.91,0l25.49,14.71c.82.48,1.69.8,2.58,1.06.19.06.37.11.55.16.87.21,1.76.34,2.65.35.04,0,.08.02.13.02.1,0,.19-.03.29-.04.83-.02,1.64-.13,2.45-.32.14-.03.28-.05.42-.09.87-.24,1.7-.59,2.5-1.03.08-.04.17-.06.25-.1l50.97-29.43c3.65-2.11,5.9-6.01,5.9-10.22v-58.86c0-4.22-2.25-8.11-5.9-10.22l-50.97-29.43c-3.65-2.11-8.15-2.11-11.81,0l-50.97,29.43c-.08.04-.13.11-.2.16-.78.48-1.51,1.02-2.15,1.66-.1.1-.18.21-.28.31-.57.6-1.08,1.26-1.51,1.97-.07.12-.15.22-.22.34-.44.77-.77,1.6-1.03,2.47-.05.19-.1.37-.14.56-.22.89-.37,1.81-.37,2.76v29.43c0,11.36-6.11,21.95-15.95,27.63-9.84,5.68-22.06,5.68-31.91,0l-25.49-14.71c-.82-.48-1.69-.8-2.57-1.06-.19-.06-.37-.11-.56-.16-.88-.21-1.76-.34-2.65-.34-.13,0-.26.02-.4.02-.84.02-1.66.13-2.47.32-.13.03-.27.05-.4.09-.87.24-1.71.6-2.51,1.04-.08.04-.16.06-.24.1l-50.97,29.43c-3.65,2.11-5.9,6.01-5.9,10.22v58.86c0,4.22,2.25,8.11,5.9,10.22l50.97,29.43c.08.04.17.06.24.1.8.44,1.64.79,2.5,1.03.14.04.28.06.42.09.81.19,1.62.3,2.45.32.1,0,.19.04.29.04.04,0,.08-.02.13-.02.89,0,1.77-.13,2.65-.35.19-.04.37-.1.56-.16.88-.26,1.75-.59,2.58-1.06l25.49-14.71c9.84-5.68,22.06-5.68,31.91,0,9.84,5.68,15.95,16.27,15.95,27.63v29.43c0,.95.15,1.87.37,2.76.05.19.09.37.14.56.25.86.59,1.69,1.03,2.47.07.12.15.22.22.34.43.71.94,1.37,1.51,1.97.1.1.18.21.28.31.65.63,1.37,1.18,2.15,1.66.07.04.13.11.2.16l50.97,29.43c1.83,1.05,3.86,1.58,5.9,1.58s4.08-.53,5.9-1.58l50.97-29.43c3.65-2.11,5.9-6.01,5.9-10.22v-58.86c0-4.22-2.25-8.11-5.9-10.22l-50.97-29.43c-.08-.04-.16-.06-.24-.1-.8-.44-1.64-.8-2.51-1.04-.13-.04-.26-.05-.39-.09-.82-.2-1.65-.31-2.49-.33-.13,0-.25-.02-.38-.02-.89,0-1.78.13-2.66.35-.18.04-.36.1-.54.15-.88.26-1.75.59-2.58,1.07l-25.49,14.72c-9.84,5.68-22.07,5.68-31.9,0-9.84-5.68-15.95-16.27-15.95-27.63s6.11-21.95,15.95-27.63Z" fill="rgb(33, 193, 154)"></path>
            <path d="M141.09,317.65l50.97,29.43c1.83,1.05,3.86,1.58,5.9,1.58s4.08-.53,5.9-1.58l50.97-29.43c.08-.04.13-.11.2-.16.78-.48,1.51-1.02,2.15-1.66.1-.1.18-.21.28-.31.57-.6,1.08-1.26,1.51-1.97.07-.12.15-.22.22-.34.44-.77.77-1.6,1.03-2.47.05-.19.1-.37.14-.56.22-.89.37-1.81.37-2.76v-29.43c0-11.36,6.11-21.95,15.96-27.63s22.06-5.68,31.91,0l25.49,14.71c.82.48,1.69.8,2.57,1.06.19.06.37.11.56.16.87.21,1.76.34,2.64.35.04,0,.09.02.13.02.1,0,.19-.04.29-.04.83-.02,1.65-.13,2.45-.32.14-.03.28-.05.41-.09.87-.24,1.71-.6,2.51-1.04.08-.04.16-.06.24-.1l50.97-29.43c3.65-2.11,5.9-6.01,5.9-10.22v-58.86c0-4.22-2.25-8.11-5.9-10.22l-50.97-29.43c-3.65-2.11-8.15-2.11-11.81,0l-50.97,29.43c-.08.04-.13.11-.2.16-.78.48-1.51,1.02-2.15,1.66-.1.1-.18.21-.28.31-.57.6-1.08,1.26-1.51,1.97-.07.12-.15.22-.22.34-.44.77-.77,1.6-1.03,2.47-.05.19-.1.37-.14.56-.22.89-.37,1.81-.37,2.76v29.43c0,11.36-6.11,21.95-15.95,27.63-9.84,5.68-22.07,5.68-31.91,0l-25.49-14.71c-.82-.48-1.69-.8-2.58-1.06-.19-.06-.37-.11-.55-.16-.88-.21-1.76-.34-2.65-.35-.13,0-.26.02-.4.02-.83.02-1.66.13-2.47.32-.13.03-.27.05-.4.09-.87.24-1.71.6-2.51,1.04-.08.04-.16.06-.24.1l-50.97,29.43c-3.65,2.11-5.9,6.01-5.9,10.22v58.86c0,4.22,2.25,8.11,5.9,10.22Z" fill="rgb(57, 105, 202)"></path>
            <path d="M396.88,484.35l-50.97-29.43c-.08-.04-.17-.06-.24-.1-.8-.44-1.64-.79-2.51-1.03-.14-.04-.27-.06-.41-.09-.81-.19-1.64-.3-2.47-.32-.13,0-.26-.02-.39-.02-.89,0-1.78.13-2.66.35-.18.04-.36.1-.54.15-.88.26-1.76.59-2.58,1.07l-25.49,14.72c-9.84,5.68-22.06,5.68-31.9,0-9.84-5.68-15.96-16.27-15.96-27.63v-29.43c0-.95-.15-1.87-.37-2.76-.05-.19-.09-.37-.14-.56-.25-.86-.59-1.69-1.03-2.47-.07-.12-.15-.22-.22-.34-.43-.71-.94-1.37-1.51-1.97-.1-.1-.18-.21-.28-.31-.65-.63-1.37-1.18-2.15-1.66-.07-.04-.13-.11-.2-.16l-50.97-29.43c-3.65-2.11-8.15-2.11-11.81,0l-50.97,29.43c-3.65,2.11-5.9,6.01-5.9,10.22v58.86c0,4.22,2.25,8.11,5.9,10.22l50.97,29.43c.08.04.17.06.25.1.8.44,1.63.79,2.5,1.03.14.04.29.06.43.09.8.19,1.61.3,2.43.32.1,0,.2.04.3.04.04,0,.09-.02.13-.02.88,0,1.77-.13,2.64-.34.19-.04.37-.1.56-.16.88-.26,1.75-.59,2.57-1.06l25.49-14.71c9.84-5.68,22.06-5.68,31.91,0,9.84,5.68,15.95,16.27,15.95,27.63v29.43c0,.95.15,1.87.37,2.76.05.19.09.37.14.56.25.86.59,1.69,1.03,2.47.07.12.15.22.22.34.43.71.94,1.37,1.51,1.97.1.1.18.21.28.31.65.63,1.37,1.18,2.15,1.66.07.04.13.11.2.16l50.97,29.43c1.83,1.05,3.86,1.58,5.9,1.58s4.08-.53,5.9-1.58l50.97-29.43c3.65-2.11,5.9-6.01,5.9-10.22v-58.86c0-4.22-2.25-8.11-5.9-10.22Z" fill="rgb(2, 148, 222)"></path>
        </svg>`;
        return createCustomButton('deepwikiButton', deepwikiUrl, iconHtml, 'DeepWiki', iconOnly);
    }

    // 统一处理按钮创建和添加的函数
    async function addButtons() {
        try {
            // 查找合适的按钮容器
            const buttonContainer = await findButtonContainer();
            if (!buttonContainer) {
                console.log('GitHub按钮脚本:找不到合适的按钮容器');
                return;
            }

            // 移除可能已存在的按钮
            const existingButtons = document.querySelectorAll('#githubdevButton, #zreadaiButton, #deepwikiButton');
            existingButtons.forEach(btn => btn.remove());

            // 检测现有按钮数量,决定是否使用仅图标模式
            const existingButtonCount = countExistingButtons(buttonContainer);
            const iconOnly = existingButtonCount > 3;

            // 创建按钮并添加到容器
            const deepWikiButton = createDeepWikiButton(iconOnly);
            const zreadAiButton = createZreadAiButton(iconOnly);
            const githubDevButton = createGithubDevButton(iconOnly);

            // 使用正确的顺序添加按钮,保证它们显示在最前面
            buttonContainer.insertBefore(deepWikiButton, buttonContainer.firstChild);
            buttonContainer.insertBefore(zreadAiButton, buttonContainer.firstChild);
            buttonContainer.insertBefore(githubDevButton, buttonContainer.firstChild);
        } catch (error) {
            console.log('GitHub按钮脚本:添加按钮时发生错误', error);
        }
    }

    // 防抖函数
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // 使用防抖的按钮添加函数
    const debouncedAddButtons = debounce(addButtons, 300);

    // 页面加载完成后执行
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', debouncedAddButtons);
    } else {
        // 页面已经加载完成,延迟执行以确保所有元素都渲染完成
        setTimeout(debouncedAddButtons, 100);
    }

    // 监听 PJAX/Turbo 导航事件
    document.addEventListener("pjax:end", debouncedAddButtons);
    document.addEventListener("turbo:load", debouncedAddButtons); // 增加 Turbo 事件支持

    // 监听 URL 变化
    let currentUrl = location.href;
    const observer = new MutationObserver(() => {
        if (location.href !== currentUrl) {
            currentUrl = location.href;
            setTimeout(debouncedAddButtons, 500);
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();