GitHub DeepWiki Button

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

Από την 06/05/2025. Δείτε την τελευταία έκδοση.

// ==UserScript==
// @name         GitHub DeepWiki Button
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  在 GitHub 仓库页面的 Star/Watch/Fork 按钮旁添加 DeepWiki 按钮,方便一键跳转到对应仓库的 DeepWiki 页面。
// @author       nuttycc
// @match        https://github.com/*/*
// @icon         https://deepwiki.com/favicon.ico
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // 配置
  const CONFIG = {
    DEBUG: false, // 设置为 true 开启调试日志
    DEBOUNCE_DELAY: 300, // 防抖延迟时间(毫秒)
  };

  // 缓存常用数据
  const CACHE = {
    excludedPaths: [
      "settings",
      "marketplace",
      "explore",
      "topics",
      "trending",
      "collections",
      "events",
      "sponsors",
      "notifications",
    ],
    // 预定义 SVG 图标
    svgIcon: `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="margin-right:4px">
      <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>
      <path d="M7 9h10v1H7zm0 4h10v1H7z"></path>
    </svg>`,
  };

  // 日志函数,只在调试模式下输出
  function log(...args) {
    if (CONFIG.DEBUG) {
      console.debug("[DeepWiki]", ...args);
    }
  }

  // 防抖函数
  function debounce(func, delay) {
    let timer;
    return function (...args) {
      clearTimeout(timer);
      timer = setTimeout(() => func.apply(this, args), delay);
    };
  }

  // 获取仓库路径
  function getRepoPath() {
    const pathParts = window.location.pathname.split("/").filter(Boolean);
    if (pathParts.length < 2) return null;
    return `${pathParts[0]}/${pathParts[1]}`;
  }

  // 检查当前页面是否是仓库页面
  function isRepoPage() {
    // 快速检查 URL 格式
    const pathParts = window.location.pathname.split("/").filter(Boolean);

    // 必须至少有用户名和仓库名两部分
    if (pathParts.length < 2) {
      return false;
    }

    // 排除特殊页面
    if (CACHE.excludedPaths.includes(pathParts[0])) {
      return false;
    }

    // 检查是否存在 Star/Watch/Fork 按钮容器
    const buttonContainer = document.querySelector("ul.pagehead-actions");
    const isRepo = !!buttonContainer;

    log(
      `[isRepoPage] 检测仓库页面: 路径:${pathParts.join(
        "/"
      )}, 按钮容器是否存在:${isRepo}`
    );
    return isRepo;
  }

  // 创建 DeepWiki 按钮
  function createDeepWikiButton(repoPath) {
    // 使用 DocumentFragment 提高性能
    const fragment = document.createDocumentFragment();

    // 创建列表项
    const listItem = document.createElement("li");
    listItem.className = "deepwiki-button d-flex";

    // 创建按钮
    const button = document.createElement("a");
    button.href = `https://deepwiki.com/${repoPath}`;
    button.className = "btn btn-sm";
    button.target = "_blank";
    button.rel = "noopener noreferrer";
    button.style.display = "flex";
    button.style.alignItems = "center";
    button.style.gap = "4px";

    // 使用预定义的 SVG 图标
    button.innerHTML = CACHE.svgIcon;

    // 添加文本
    const text = document.createElement("span");
    text.textContent = "DeepWiki";
    button.appendChild(text);

    // 组装
    listItem.appendChild(button);
    fragment.appendChild(listItem);

    return fragment;
  }

  // 添加 DeepWiki 按钮
  function addDeepWikiButton() {
    // 如果按钮已存在,则不再添加
    if (document.querySelector(".deepwiki-button")) {
      log("按钮已存在,跳过添加。");
      return;
    }

    // 获取仓库路径
    const repoPath = getRepoPath();
    if (!repoPath) {
      log("路径不符合要求,跳过添加。");
      return;
    }

    log(`[addDeepWikiButton] 检测到仓库路径: ${repoPath}`);

    // 获取按钮容器
    const buttonContainer = document.querySelector("ul.pagehead-actions");
    if (!buttonContainer) {
      log("未找到 ul.pagehead-actions 容器,跳过添加。");
      return;
    }

    // 创建并添加按钮
    const buttonFragment = createDeepWikiButton(repoPath);
    buttonContainer.insertBefore(buttonFragment, buttonContainer.firstChild);

    log("🎉 按钮添加成功。");
  }

  // 处理页面变化的统一函数
  const handlePageChange = debounce(() => {
    if (isRepoPage()) {
      addDeepWikiButton();
    }
  }, CONFIG.DEBOUNCE_DELAY);

  // 初始化函数
  function init() {
    // 页面加载完成时检查
    window.addEventListener("load", () => {
      log("[event] load 事件触发");
      handlePageChange();
    });

    // 监听 PJAX 结束事件
    document.addEventListener("pjax:end", () => {
      log("[event] pjax:end 事件触发");
      handlePageChange();
    });

    // 监听 turbo:render 事件
    document.addEventListener("turbo:render", () => {
      log("[event] turbo:render 事件触发");
      handlePageChange();
    });

    // 使用 turbo:render 监听变化已经足够。故移除下面内容。
    // 使用更精确的 MutationObserver 监听 DOM 变化。
    // let lastUrl = location.href;
    // const urlObserver = new MutationObserver(() => {
    //   const url = location.href;
    //   if (url !== lastUrl) {
    //     lastUrl = url;
    //     log("[Observer CallBack] URL 变化:", url);
    //     handlePageChange();
    //   }
    // });

    // // 只观察 body 元素,减少不必要的回调
    // const observeTarget = document.querySelector("body");
    // if (observeTarget) {
    //   urlObserver.observe(observeTarget, {
    //     childList: true,
    //     subtree: true,
    //   });
    // }

    // 初始检查
    log("[init] 初始检查。");
    handlePageChange();
  }

  // 启动脚本
  init();
})();