GitHub to DeepWiki

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

// ==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));
    }

})();