GitHub to DeepWiki

Adds a button to GitHub repo pages to open the corresponding DeepWiki page.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         GitHub to DeepWiki
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Adds a button to GitHub repo pages to open the corresponding DeepWiki page.
// @description:zh-CN 在 GitHub 仓库页面添加按钮,以快速打开对应的 DeepWiki 页面。
// @author       Leihao Zhou
// @match        https://github.com/*/*
// @grant        GM_addStyle
// @grant        window.open
// @run-at       document-idle
// @name:zh-CN   GitHub 跳转 DeepWiki
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const BUTTON_ID = 'deepwiki-button-userscript'; // Unique ID

    const addDeepWikiButton = () => {
        // 1. Check if already added
        if (document.getElementById(BUTTON_ID)) {
            return;
        }

        // 2. Check if on a valid repo page (path needs at least user/repo)
        const pathParts = location.pathname.split('/').filter(Boolean);
        // Ensure it's a repo page, not settings, issues, PRs etc. within the repo for simplicity
        // A more robust check might involve looking for specific page elements.
        if (pathParts.length !== 2) {
             // console.log('DeepWiki Button: Not on a main repo page.');
             return; // Only target the main repo page for now
        }

        // 3. Find insertion point (try a few common selectors for GitHub's layout)
        const potentialTargets = [
            '.gh-header-actions', // Newer layout
            '.pagehead-actions', // Older layout
            '#repository-container-header > div > div > div > ul' // Fallback selector observed
        ];
        let targetElement = null;
        for (const selector of potentialTargets) {
            targetElement = document.querySelector(selector);
            if (targetElement) break;
        }

        if (!targetElement) {
            // console.log('DeepWiki Button: Target element not found using selectors:', potentialTargets);
            // Try again after a short delay in case the element is slow to render
            setTimeout(() => {
                if (!document.getElementById(BUTTON_ID)) { // Check again before retrying
                     targetElement = document.querySelector(potentialTargets.join(', ')); // Try all at once
                     if (targetElement) {
                         insertButton(targetElement);
                     } else {
                         console.warn('DeepWiki Button: Target element still not found after delay.');
                     }
                }
            }, 1000); // Wait 1 second
            return;
        }

        insertButton(targetElement);
    };

    const insertButton = (targetElement) => {
         // 4. Create button
        const button = document.createElement('a');
        button.id = BUTTON_ID;
        button.textContent = '🚀 Open in DeepWiki';
        button.target = '_blank';
        button.rel = 'noopener noreferrer';
        button.href = '#'; // Set href to '#' initially to make it behave like a link
        button.setAttribute('aria-label', 'Open this repository in DeepWiki');
        button.setAttribute('role', 'button');
        button.setAttribute('tabindex', '0'); // Make it focusable

        // Apply styles using GM_addStyle for better management or inline styles
        // Using inline styles for simplicity here
        button.style.marginLeft = '8px';
        button.style.padding = '5px 16px'; // Adjusted padding like GitHub buttons
        button.style.border = '1px solid rgba(240, 246, 252, 0.1)'; // GitHub's border color
        button.style.borderRadius = '6px';
        button.style.backgroundColor = '#21262d'; // GitHub's dark button background
        button.style.color = '#c9d1d9'; // GitHub's dark button text color
        button.style.fontWeight = '500';
        button.style.fontSize = '14px'; // Match GitHub button font size
        button.style.lineHeight = '20px';
        button.style.cursor = 'pointer';
        button.style.textDecoration = 'none';
        button.style.display = 'inline-flex';
        button.style.alignItems = 'center';
        button.style.verticalAlign = 'middle'; // Ensure vertical alignment

        // Add hover/focus effect mimicking GitHub
        const hoverBg = '#30363d';
        const hoverBorder = '#8b949e';
        const defaultBg = '#21262d';
        const defaultBorder = 'rgba(240, 246, 252, 0.1)';

        button.onmouseover = () => { button.style.backgroundColor = hoverBg; button.style.borderColor = hoverBorder; };
        button.onmouseout = () => { button.style.backgroundColor = defaultBg; button.style.borderColor = defaultBorder; };
        button.onfocus = () => { button.style.outline = '2px solid #58a6ff'; button.style.outlineOffset = '2px'; }; // Accessibility focus ring
        button.onblur = () => { button.style.outline = 'none'; };


        const handleClick = (event) => {
            event.preventDefault(); // Prevent default link navigation
            event.stopPropagation(); // Stop event bubbling
            const currentUrl = location.href;
            // More robust replacement: ensure we only replace the domain and the base path
            const urlObject = new URL(currentUrl);
            const pathParts = urlObject.pathname.split('/').filter(Boolean);
            if (pathParts.length >= 2) {
                const user = pathParts[0];
                const repo = pathParts[1];
                const deepwikiUrl = `https://deepwiki.com/${user}/${repo}`;
                 window.open(deepwikiUrl, '_blank');
            } else {
                console.error('DeepWiki Button: Could not extract user/repo from URL:', currentUrl);
            }
        };

        // 5. Add click handler
        button.addEventListener('click', handleClick);
        // Add keydown handler for accessibility (Enter/Space)
        button.addEventListener('keydown', (event) => {
            if (event.key === 'Enter' || event.key === ' ') {
                handleClick(event);
            }
        });


        // 6. Insert button (prepend to appear first in the actions list, or append if prepend not suitable)
        // Using prepend is generally better for visibility
        if (targetElement.prepend) {
             targetElement.prepend(button);
        } else {
            targetElement.insertBefore(button, targetElement.firstChild); // Fallback for older browsers
        }
        console.log('DeepWiki Button added.');
    }


    // --- Handling SPA Navigation ---
    // GitHub uses Turbo (formerly Turbolinks) for navigation. Observe changes to the body or a specific container.
    let previousUrl = location.href;
    const observer = new MutationObserver((mutationsList) => {
        // Check if URL changed - simple way to detect SPA navigation
        if (location.href !== previousUrl) {
            previousUrl = location.href;
            // Wait a bit for the new page elements to likely render after URL change
            setTimeout(addDeepWikiButton, 300);
        } else {
             // If URL didn't change, check if the button is missing and the target exists (e.g., partial DOM update)
             if (!document.getElementById(BUTTON_ID)) {
                 const target = document.querySelector('.gh-header-actions, .pagehead-actions, #repository-container-header > div > div > div > ul');
                 if (target) {
                     addDeepWikiButton(); // Try adding if target exists but button doesn't
                 }
             }
        }
    });

    // Start observing the body for subtree modifications and child list changes.
    // Observing 'body' is broad but reliable for catching SPA navigations.
    observer.observe(document.body, { childList: true, subtree: true });

    // Initial run in case the page is already loaded
    // Use requestIdleCallback or setTimeout for potentially better timing
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        setTimeout(addDeepWikiButton, 500); // Delay slightly
    } else {
        document.addEventListener('DOMContentLoaded', () => setTimeout(addDeepWikiButton, 500));
    }

})();