// ==UserScript==
// @name GitHub 增强工具栏
// @icon https://github.com/favicons/favicon.svg
// @version 1.5.0
// @description 在 Github 网站顶部显示 Github.dev 和 DeepWiki 和 ZreadAi 按钮,方便更好地查看代码。当按钮过多时自动切换为图标模式。
// @author Txy-Sky
// @match https://github.com/*
// @run-at document-end
// @grant none
// @namespace https://github.com/txy-sky
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
// 添加全局样式以修复按钮样式
const style = `
.custom-github-button {
margin: 0 4px;
display: flex;
align-items: center;
height: 28px;
}
.custom-github-button .octicon {
margin-right: 4px;
vertical-align: text-bottom;
}
.custom-github-button.icon-only .octicon {
margin-right: 0;
}
.pagehead-actions > li {
margin-right: 8px;
}
`;
// 注入样式到页面
const styleElement = document.createElement('style');
styleElement.textContent = style;
document.head.appendChild(styleElement);
// 等待元素出现的函数
function waitForElement(selector, timeout = 10000) {
return new Promise((resolve, reject) => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
obs.disconnect();
resolve(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// 超时处理
setTimeout(() => {
observer.disconnect();
reject(new Error(`Element ${selector} not found within ${timeout}ms`));
}, timeout);
});
}
// 查找按钮容器的函数,提高兼容性
async function findButtonContainer() {
// 尝试多个可能的选择器
const selectors = [
'ul.pagehead-actions',
'.pagehead-actions',
'.file-navigation .d-flex',
'nav[aria-label="Repository"] .d-flex'
];
for (const selector of selectors) {
const element = document.querySelector(selector);
if (element) return element;
}
return null;
}
// 检测现有按钮数量的函数
function countExistingButtons(container) {
if (!container) return 0;
// 计算现有按钮数量,排除我们即将添加的按钮
const existingButtons = container.querySelectorAll('li:not(#githubdevButton):not(#zreadaiButton):not(#deepwikiButton)');
return existingButtons.length;
}
// 统一按钮创建函数,支持仅图标模式
function createCustomButton(id, url, iconHtml, text, iconOnly = false) {
const li = document.createElement('li');
li.id = id;
li.className = 'd-flex';
li.style.marginRight = '8px';
const a = document.createElement('a');
a.href = url;
a.className = iconOnly ? 'btn btn-sm custom-github-button icon-only' : 'btn btn-sm custom-github-button';
a.target = '_blank';
a.rel = 'noopener noreferrer';
if (iconOnly) {
a.innerHTML = iconHtml;
a.title = text; // 添加 tooltip 显示完整文本
} else {
a.innerHTML = `${iconHtml}${text}`;
}
li.appendChild(a);
return li;
}
// 创建 Github.dev 按钮
function createGithubDevButton(iconOnly = false) {
const githubdevUrl = `https://github.dev${location.pathname}${location.search}${location.hash}`;
const iconHtml = `
`;
return createCustomButton('githubdevButton', githubdevUrl, iconHtml, 'Github.dev', iconOnly);
}
// 创建 ZreadAi 按钮
function createZreadAiButton(iconOnly = false) {
const zreadAiUrl = `https://zread.ai${location.pathname}${location.search}${location.hash}`;
const iconHtml = ``;
return createCustomButton('zreadaiButton', zreadAiUrl, iconHtml, 'ZreadAi', iconOnly);
}
// 创建 DeepWiki 按钮
function createDeepWikiButton(iconOnly = false) {
const deepwikiUrl = `https://deepwiki.com${location.pathname}${location.search}${location.hash}`;
const iconHtml = ``;
return createCustomButton('deepwikiButton', deepwikiUrl, iconHtml, 'DeepWiki', iconOnly);
}
// 统一处理按钮创建和添加的函数
async function addButtons() {
try {
// 查找合适的按钮容器
const buttonContainer = await findButtonContainer();
if (!buttonContainer) {
console.log('GitHub按钮脚本:找不到合适的按钮容器');
return;
}
// 移除可能已存在的按钮
const existingButtons = document.querySelectorAll('#githubdevButton, #zreadaiButton, #deepwikiButton');
existingButtons.forEach(btn => btn.remove());
// 检测现有按钮数量,决定是否使用仅图标模式
const existingButtonCount = countExistingButtons(buttonContainer);
const iconOnly = existingButtonCount > 3;
// 创建按钮并添加到容器
const deepWikiButton = createDeepWikiButton(iconOnly);
const zreadAiButton = createZreadAiButton(iconOnly);
const githubDevButton = createGithubDevButton(iconOnly);
// 使用正确的顺序添加按钮,保证它们显示在最前面
buttonContainer.insertBefore(deepWikiButton, buttonContainer.firstChild);
buttonContainer.insertBefore(zreadAiButton, buttonContainer.firstChild);
buttonContainer.insertBefore(githubDevButton, buttonContainer.firstChild);
} catch (error) {
console.log('GitHub按钮脚本:添加按钮时发生错误', error);
}
}
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 使用防抖的按钮添加函数
const debouncedAddButtons = debounce(addButtons, 300);
// 页面加载完成后执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', debouncedAddButtons);
} else {
// 页面已经加载完成,延迟执行以确保所有元素都渲染完成
setTimeout(debouncedAddButtons, 100);
}
// 监听 PJAX/Turbo 导航事件
document.addEventListener("pjax:end", debouncedAddButtons);
document.addEventListener("turbo:load", debouncedAddButtons); // 增加 Turbo 事件支持
// 监听 URL 变化
let currentUrl = location.href;
const observer = new MutationObserver(() => {
if (location.href !== currentUrl) {
currentUrl = location.href;
setTimeout(debouncedAddButtons, 500);
}
});
observer.observe(document.body, { childList: true, subtree: true });
})();