GitHub → Deepwiki 跳转

在 github.com 页面注入按钮,点击后跳转到 deepwiki.com。

// ==UserScript==
// @name         GitHub → Deepwiki 跳转
// @namespace    http://tampermonkey.net/
// @version      2025-09-10
// @description  在 github.com 页面注入按钮,点击后跳转到 deepwiki.com。
// @author       ranxiu
// @match        *://*.github.com/*
// @icon         https://deepwiki.com/favicon.ico
// @grant        none
// @run-at       document-idle
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    if (location.hostname !== 'github.com') {
        return;
    }

    function buildTargetUrlFromCurrent() {
        const currentUrl = new URL(location.href);
        if (currentUrl.hostname !== 'github.com') {
            return null;
        }
        currentUrl.hostname = 'deepwiki.com';
        return currentUrl.toString();
    }

    function createFloatingButton() {
        const button = document.createElement('button');
        button.type = 'button';
        button.textContent = '在 deepwiki 打开';
        button.title = '将当前 GitHub 页面在 deepwiki.com 打开';
        button.setAttribute('aria-label', '在 deepwiki 打开');
        Object.assign(button.style, {
            display: 'inline-block',
            marginLeft: '8px',
            padding: '3px 12px',
            borderRadius: '6px',
            border: '1px solid rgba(27, 31, 36, 0.15)',
            background: 'var(--button-default-bgColor-rest, #f6f8fa)',
            color: 'var(--fgColor-default, #24292f)',
            fontSize: '12px',
            fontWeight: '500',
            lineHeight: '20px',
            boxShadow: 'var(--shadow-resting-small, 0 1px 0 rgba(27,31,36,0.04))',
            cursor: 'pointer',
            verticalAlign: 'middle'
        });
        button.addEventListener('mouseenter', function () {
            button.style.background = 'var(--button-default-bgColor-hover, #f3f4f6)';
        });
        button.addEventListener('mouseleave', function () {
            button.style.background = 'var(--button-default-bgColor-rest, #f6f8fa)';
        });
        button.addEventListener('mousedown', function () {
            button.style.background = 'var(--button-default-bgColor-active, #edeff2)';
        });
        button.addEventListener('mouseup', function () {
            button.style.background = 'var(--button-default-bgColor-hover, #f3f4f6)';
        });
        button.addEventListener('focus', function () {
            button.style.outline = '2px solid var(--focus-outlineColor, #0969da)';
            button.style.outlineOffset = '2px';
        });
        button.addEventListener('blur', function () {
            button.style.outline = 'none';
            button.style.outlineOffset = '0';
        });
        button.addEventListener('click', function (ev) {
            ev.preventDefault();
            const targetUrl = buildTargetUrlFromCurrent();
            if (!targetUrl) {
                return;
            }
            if (location.href === targetUrl) {
                return;
            }
            location.replace(targetUrl);
        });
        return button;
    }

    function findAboutHeaderElement() {
        // 优先在侧边栏查找,避免误匹配 README 中的 About
        const sidebar = document.querySelector('.Layout-sidebar');
        if (sidebar) {
            const headings = sidebar.querySelectorAll('h2, h3');
            for (const heading of headings) {
                const text = (heading.textContent || '').trim();
                if (text === 'About') {
                    return heading;
                }
            }
        }
        // 兜底:全局查找,但排除 markdown 正文区域
        const headings = document.querySelectorAll('h2, h3');
        for (const heading of headings) {
            if (heading.closest('.markdown-body')) {
                continue; // 跳过 README/issue 正文
            }
            const text = (heading.textContent || '').trim();
            if (text === 'About') {
                return heading;
            }
        }
        return null;
    }

    function injectButtonOnce() {
        if (document.getElementById('tm-deepwiki-redirect')) {
            return;
        }
        const anchor = findAboutHeaderElement();
        if (!anchor) {
            return;
        }
        const btn = createFloatingButton();
        btn.id = 'tm-deepwiki-redirect';
        // 将按钮放到 About 标题右侧
        anchor.appendChild(btn);
    }

    // 兼容 GitHub 的 PJAX/Turbo 导航:监听主容器变化,缺失时重新注入
    const observer = new MutationObserver(function () {
        if (!document.getElementById('tm-deepwiki-redirect')) {
            injectButtonOnce();
        }
    });
    observer.observe(document.documentElement, { childList: true, subtree: true });

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', injectButtonOnce);
    } else {
        injectButtonOnce();
    }
})();