GitHub 跳转 DeepWiki

在 GitHub 仓库页面添加按钮,以快速打开对应的 DeepWiki 页面。

  1. // ==UserScript==
  2. // @name GitHub to DeepWiki
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description Adds a button to GitHub repo pages to open the corresponding DeepWiki page.
  6. // @description:zh-CN 在 GitHub 仓库页面添加按钮,以快速打开对应的 DeepWiki 页面。
  7. // @author Leihao Zhou
  8. // @match https://github.com/*/*
  9. // @grant GM_addStyle
  10. // @grant window.open
  11. // @run-at document-idle
  12. // @name:zh-CN GitHub 跳转 DeepWiki
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. const BUTTON_ID = 'deepwiki-button-userscript'; // Unique ID
  20.  
  21. const addDeepWikiButton = () => {
  22. // 1. Check if already added
  23. if (document.getElementById(BUTTON_ID)) {
  24. return;
  25. }
  26.  
  27. // 2. Check if on a valid repo page (path needs at least user/repo)
  28. const pathParts = location.pathname.split('/').filter(Boolean);
  29. // Ensure it's a repo page, not settings, issues, PRs etc. within the repo for simplicity
  30. // A more robust check might involve looking for specific page elements.
  31. if (pathParts.length !== 2) {
  32. // console.log('DeepWiki Button: Not on a main repo page.');
  33. return; // Only target the main repo page for now
  34. }
  35.  
  36. // 3. Find insertion point (try a few common selectors for GitHub's layout)
  37. const potentialTargets = [
  38. '.gh-header-actions', // Newer layout
  39. '.pagehead-actions', // Older layout
  40. '#repository-container-header > div > div > div > ul' // Fallback selector observed
  41. ];
  42. let targetElement = null;
  43. for (const selector of potentialTargets) {
  44. targetElement = document.querySelector(selector);
  45. if (targetElement) break;
  46. }
  47.  
  48. if (!targetElement) {
  49. // console.log('DeepWiki Button: Target element not found using selectors:', potentialTargets);
  50. // Try again after a short delay in case the element is slow to render
  51. setTimeout(() => {
  52. if (!document.getElementById(BUTTON_ID)) { // Check again before retrying
  53. targetElement = document.querySelector(potentialTargets.join(', ')); // Try all at once
  54. if (targetElement) {
  55. insertButton(targetElement);
  56. } else {
  57. console.warn('DeepWiki Button: Target element still not found after delay.');
  58. }
  59. }
  60. }, 1000); // Wait 1 second
  61. return;
  62. }
  63.  
  64. insertButton(targetElement);
  65. };
  66.  
  67. const insertButton = (targetElement) => {
  68. // 4. Create button
  69. const button = document.createElement('a');
  70. button.id = BUTTON_ID;
  71. button.textContent = '🚀 Open in DeepWiki';
  72. button.target = '_blank';
  73. button.rel = 'noopener noreferrer';
  74. button.href = '#'; // Set href to '#' initially to make it behave like a link
  75. button.setAttribute('aria-label', 'Open this repository in DeepWiki');
  76. button.setAttribute('role', 'button');
  77. button.setAttribute('tabindex', '0'); // Make it focusable
  78.  
  79. // Apply styles using GM_addStyle for better management or inline styles
  80. // Using inline styles for simplicity here
  81. button.style.marginLeft = '8px';
  82. button.style.padding = '5px 16px'; // Adjusted padding like GitHub buttons
  83. button.style.border = '1px solid rgba(240, 246, 252, 0.1)'; // GitHub's border color
  84. button.style.borderRadius = '6px';
  85. button.style.backgroundColor = '#21262d'; // GitHub's dark button background
  86. button.style.color = '#c9d1d9'; // GitHub's dark button text color
  87. button.style.fontWeight = '500';
  88. button.style.fontSize = '14px'; // Match GitHub button font size
  89. button.style.lineHeight = '20px';
  90. button.style.cursor = 'pointer';
  91. button.style.textDecoration = 'none';
  92. button.style.display = 'inline-flex';
  93. button.style.alignItems = 'center';
  94. button.style.verticalAlign = 'middle'; // Ensure vertical alignment
  95.  
  96. // Add hover/focus effect mimicking GitHub
  97. const hoverBg = '#30363d';
  98. const hoverBorder = '#8b949e';
  99. const defaultBg = '#21262d';
  100. const defaultBorder = 'rgba(240, 246, 252, 0.1)';
  101.  
  102. button.onmouseover = () => { button.style.backgroundColor = hoverBg; button.style.borderColor = hoverBorder; };
  103. button.onmouseout = () => { button.style.backgroundColor = defaultBg; button.style.borderColor = defaultBorder; };
  104. button.onfocus = () => { button.style.outline = '2px solid #58a6ff'; button.style.outlineOffset = '2px'; }; // Accessibility focus ring
  105. button.onblur = () => { button.style.outline = 'none'; };
  106.  
  107.  
  108. const handleClick = (event) => {
  109. event.preventDefault(); // Prevent default link navigation
  110. event.stopPropagation(); // Stop event bubbling
  111. const currentUrl = location.href;
  112. // More robust replacement: ensure we only replace the domain and the base path
  113. const urlObject = new URL(currentUrl);
  114. const pathParts = urlObject.pathname.split('/').filter(Boolean);
  115. if (pathParts.length >= 2) {
  116. const user = pathParts[0];
  117. const repo = pathParts[1];
  118. const deepwikiUrl = `https://deepwiki.com/${user}/${repo}`;
  119. window.open(deepwikiUrl, '_blank');
  120. } else {
  121. console.error('DeepWiki Button: Could not extract user/repo from URL:', currentUrl);
  122. }
  123. };
  124.  
  125. // 5. Add click handler
  126. button.addEventListener('click', handleClick);
  127. // Add keydown handler for accessibility (Enter/Space)
  128. button.addEventListener('keydown', (event) => {
  129. if (event.key === 'Enter' || event.key === ' ') {
  130. handleClick(event);
  131. }
  132. });
  133.  
  134.  
  135. // 6. Insert button (prepend to appear first in the actions list, or append if prepend not suitable)
  136. // Using prepend is generally better for visibility
  137. if (targetElement.prepend) {
  138. targetElement.prepend(button);
  139. } else {
  140. targetElement.insertBefore(button, targetElement.firstChild); // Fallback for older browsers
  141. }
  142. console.log('DeepWiki Button added.');
  143. }
  144.  
  145.  
  146. // --- Handling SPA Navigation ---
  147. // GitHub uses Turbo (formerly Turbolinks) for navigation. Observe changes to the body or a specific container.
  148. let previousUrl = location.href;
  149. const observer = new MutationObserver((mutationsList) => {
  150. // Check if URL changed - simple way to detect SPA navigation
  151. if (location.href !== previousUrl) {
  152. previousUrl = location.href;
  153. // Wait a bit for the new page elements to likely render after URL change
  154. setTimeout(addDeepWikiButton, 300);
  155. } else {
  156. // If URL didn't change, check if the button is missing and the target exists (e.g., partial DOM update)
  157. if (!document.getElementById(BUTTON_ID)) {
  158. const target = document.querySelector('.gh-header-actions, .pagehead-actions, #repository-container-header > div > div > div > ul');
  159. if (target) {
  160. addDeepWikiButton(); // Try adding if target exists but button doesn't
  161. }
  162. }
  163. }
  164. });
  165.  
  166. // Start observing the body for subtree modifications and child list changes.
  167. // Observing 'body' is broad but reliable for catching SPA navigations.
  168. observer.observe(document.body, { childList: true, subtree: true });
  169.  
  170. // Initial run in case the page is already loaded
  171. // Use requestIdleCallback or setTimeout for potentially better timing
  172. if (document.readyState === 'complete' || document.readyState === 'interactive') {
  173. setTimeout(addDeepWikiButton, 500); // Delay slightly
  174. } else {
  175. document.addEventListener('DOMContentLoaded', () => setTimeout(addDeepWikiButton, 500));
  176. }
  177.  
  178. })();