GitHub DeepWiki Button

在 GitHub 仓库页面的 Star/Watch/Fork 按钮旁添加 DeepWiki 按钮,方便一键跳转到对应仓库的 DeepWiki 页面。

当前为 2025-05-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GitHub DeepWiki Button
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.1
  5. // @description 在 GitHub 仓库页面的 Star/Watch/Fork 按钮旁添加 DeepWiki 按钮,方便一键跳转到对应仓库的 DeepWiki 页面。
  6. // @author nuttycc
  7. // @match https://github.com/*/*
  8. // @icon https://deepwiki.com/favicon.ico
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. "use strict";
  15.  
  16. // 配置
  17. const CONFIG = {
  18. DEBUG: false, // 设置为 true 开启调试日志
  19. DEBOUNCE_DELAY: 300, // 防抖延迟时间(毫秒)
  20. };
  21.  
  22. // 缓存常用数据
  23. const CACHE = {
  24. excludedPaths: [
  25. "settings",
  26. "marketplace",
  27. "explore",
  28. "topics",
  29. "trending",
  30. "collections",
  31. "events",
  32. "sponsors",
  33. "notifications",
  34. ],
  35. // 预定义 SVG 图标
  36. svgIcon: `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="margin-right:4px">
  37. <path d="M21 4H3a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1zm-1 14H4V6h16v12z"></path>
  38. <path d="M7 9h10v1H7zm0 4h10v1H7z"></path>
  39. </svg>`,
  40. };
  41.  
  42. // 日志函数,只在调试模式下输出
  43. function log(...args) {
  44. if (CONFIG.DEBUG) {
  45. console.debug("[DeepWiki]", ...args);
  46. }
  47. }
  48.  
  49. // 防抖函数
  50. function debounce(func, delay) {
  51. let timer;
  52. return function (...args) {
  53. clearTimeout(timer);
  54. timer = setTimeout(() => func.apply(this, args), delay);
  55. };
  56. }
  57.  
  58. // 获取仓库路径
  59. function getRepoPath() {
  60. const pathParts = window.location.pathname.split("/").filter(Boolean);
  61. if (pathParts.length < 2) return null;
  62. return `${pathParts[0]}/${pathParts[1]}`;
  63. }
  64.  
  65. // 检查当前页面是否是仓库页面
  66. function isRepoPage() {
  67. // 快速检查 URL 格式
  68. const pathParts = window.location.pathname.split("/").filter(Boolean);
  69.  
  70. // 必须至少有用户名和仓库名两部分
  71. if (pathParts.length < 2) {
  72. return false;
  73. }
  74.  
  75. // 排除特殊页面
  76. if (CACHE.excludedPaths.includes(pathParts[0])) {
  77. return false;
  78. }
  79.  
  80. // 检查是否存在 Star/Watch/Fork 按钮容器
  81. const buttonContainer = document.querySelector("ul.pagehead-actions");
  82. const isRepo = !!buttonContainer;
  83.  
  84. log(
  85. `[isRepoPage] 检测仓库页面: 路径:${pathParts.join(
  86. "/"
  87. )}, 按钮容器是否存在:${isRepo}`
  88. );
  89. return isRepo;
  90. }
  91.  
  92. // 创建 DeepWiki 按钮
  93. function createDeepWikiButton(repoPath) {
  94. // 使用 DocumentFragment 提高性能
  95. const fragment = document.createDocumentFragment();
  96.  
  97. // 创建列表项
  98. const listItem = document.createElement("li");
  99. listItem.className = "deepwiki-button d-flex";
  100.  
  101. // 创建按钮
  102. const button = document.createElement("a");
  103. button.href = `https://deepwiki.com/${repoPath}`;
  104. button.className = "btn btn-sm";
  105. button.target = "_blank";
  106. button.rel = "noopener noreferrer";
  107. button.style.display = "flex";
  108. button.style.alignItems = "center";
  109. button.style.gap = "4px";
  110.  
  111. // 使用预定义的 SVG 图标
  112. button.innerHTML = CACHE.svgIcon;
  113.  
  114. // 添加文本
  115. const text = document.createElement("span");
  116. text.textContent = "DeepWiki";
  117. button.appendChild(text);
  118.  
  119. // 组装
  120. listItem.appendChild(button);
  121. fragment.appendChild(listItem);
  122.  
  123. return fragment;
  124. }
  125.  
  126. // 添加 DeepWiki 按钮
  127. function addDeepWikiButton() {
  128. // 如果按钮已存在,则不再添加
  129. if (document.querySelector(".deepwiki-button")) {
  130. log("按钮已存在,跳过添加。");
  131. return;
  132. }
  133.  
  134. // 获取仓库路径
  135. const repoPath = getRepoPath();
  136. if (!repoPath) {
  137. log("路径不符合要求,跳过添加。");
  138. return;
  139. }
  140.  
  141. log(`[addDeepWikiButton] 检测到仓库路径: ${repoPath}`);
  142.  
  143. // 获取按钮容器
  144. const buttonContainer = document.querySelector("ul.pagehead-actions");
  145. if (!buttonContainer) {
  146. log("未找到 ul.pagehead-actions 容器,跳过添加。");
  147. return;
  148. }
  149.  
  150. // 创建并添加按钮
  151. const buttonFragment = createDeepWikiButton(repoPath);
  152. buttonContainer.insertBefore(buttonFragment, buttonContainer.firstChild);
  153.  
  154. log("🎉 按钮添加成功。");
  155. }
  156.  
  157. // 处理页面变化的统一函数
  158. const handlePageChange = debounce(() => {
  159. if (isRepoPage()) {
  160. addDeepWikiButton();
  161. }
  162. }, CONFIG.DEBOUNCE_DELAY);
  163.  
  164. // 初始化函数
  165. function init() {
  166. // 页面加载完成时检查
  167. window.addEventListener("load", () => {
  168. log("[event] load 事件触发");
  169. handlePageChange();
  170. });
  171.  
  172. // 监听 PJAX 结束事件
  173. document.addEventListener("pjax:end", () => {
  174. log("[event] pjax:end 事件触发");
  175. handlePageChange();
  176. });
  177.  
  178. // 监听 turbo:render 事件
  179. document.addEventListener("turbo:render", () => {
  180. log("[event] turbo:render 事件触发");
  181. handlePageChange();
  182. });
  183.  
  184. // 使用 turbo:render 监听变化已经足够。故移除下面内容。
  185. // 使用更精确的 MutationObserver 监听 DOM 变化。
  186. // let lastUrl = location.href;
  187. // const urlObserver = new MutationObserver(() => {
  188. // const url = location.href;
  189. // if (url !== lastUrl) {
  190. // lastUrl = url;
  191. // log("[Observer CallBack] URL 变化:", url);
  192. // handlePageChange();
  193. // }
  194. // });
  195.  
  196. // // 只观察 body 元素,减少不必要的回调
  197. // const observeTarget = document.querySelector("body");
  198. // if (observeTarget) {
  199. // urlObserver.observe(observeTarget, {
  200. // childList: true,
  201. // subtree: true,
  202. // });
  203. // }
  204.  
  205. // 初始检查
  206. log("[init] 初始检查。");
  207. handlePageChange();
  208. }
  209.  
  210. // 启动脚本
  211. init();
  212. })();