您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
融合 GitHub 增强功能:新窗口打开链接、MD 文件目录化、固定页面头部
// ==UserScript== // @name GitHub 增强套件 // @namespace http://tampermonkey.net/ // @version 0.8 // @description 融合 GitHub 增强功能:新窗口打开链接、MD 文件目录化、固定页面头部 // @author lecoler & contributors // @match *://github.com/* // @match *://gitee.com/*/* // @match *://npmjs.com/*/* // @include *.md // @icon https://github.com/favicon.ico // @license GPL-3.0 // @run-at document-start // ==/UserScript== (function() { 'use strict'; // === 通用工具和配置 === const githubRegex = /^https?:\/\/(www\.)?github\.com\//i; // GitHub 链接正则 const processedLinks = new WeakSet(); // 缓存已处理的链接 const log = (...args) => console.log('[GitHub 增强套件]', ...args); // 简化的日志函数 // =========================================================================== // === 功能1:强制在新窗口打开 GitHub 链接 === // =========================================================================== function handleNewWindowClick(e) { const link = e.target.closest('a'); if (!link || processedLinks.has(link)) return; const href = link.href; if (!href || !githubRegex.test(href) || href.includes('mailto:') || href.includes('#') || link.target === '_blank') return; e.preventDefault(); e.stopPropagation(); window.open(href, '_blank'); processedLinks.add(link); } document.addEventListener('click', handleNewWindowClick, { capture: true, passive: false }); // =========================================================================== // === 功能2:MD 文件目录化 === // =========================================================================== let $main, $menu, $button, lastPathName = '', moveStatus = false, titleHeight = 0; function createMdDom() { const css = document.createElement('style'); css.innerHTML = ` .le-md { position: fixed; top: 16%; left: 90%; z-index: 999; } .le-md-btn { width: 60px; height: 60px; border-radius: 50%; color: #fff; background: hsla(230,50%,50%,0.6); text-align: center; line-height: 60px; cursor: move; } .le-md-btn:hover { background: hsla(220,50%,47%,1); } .le-md-btn-hidden { display: none; } .hidden { height: 0 !important; min-height: 0 !important; border: 0 !important; } .le-md-left { right: 0; margin-right: 100px; } .le-md-right { left: 0; margin-left: 100px; } .le-md-top { bottom: 0; } .le-md-bottom { top: 0; } .le-md > ul { width: 200px; max-height: 700px; list-style: none; position: absolute; overflow: auto; padding-right: 10px; } .le-md > ul a { text-decoration: none; color: #909399; display: block; padding: 5px 10px; background: #f4f4f5; } .le-md li.le-md-title-active a { background: linear-gradient(-135deg, #ffcccc 0.6em, #fff 0); } .le-md li.le-md-title-active.le-md-title-active-first a { background: linear-gradient(-135deg, #ff9999 0.6em, #fff 0); color: #000; font-weight: 700; } `; document.head.appendChild(css); $main = document.createElement('div'); $button = document.createElement('div'); $menu = document.createElement('ul'); $button.innerHTML = '目录'; $button.title = '右键返回顶部'; $button.addEventListener('click', toggleMenu); $button.oncontextmenu = e => { scrollTo(0, 0); return false; }; $main.appendChild($button); $main.appendChild($menu); $main.className = 'le-md'; document.body.appendChild($main); $button.onmousedown = e => { const eleX = e.offsetX, eleY = e.offsetY; let count = 0; document.onmousemove = ev => { if (count++ > 9) moveStatus = true; const x = ev.clientX - eleX, y = ev.clientY - eleY; const winWidth = document.documentElement.clientWidth, winHeight = document.documentElement.clientHeight; $main.style.left = `${(x / winWidth * 100).toFixed(3)}%`; $main.style.top = `${(y / winHeight * 100).toFixed(3)}%`; }; $button.onmouseup = $button.onmouseout = () => document.onmousemove = null; }; window.onresize = () => { if (!$menu.className.match(/hidden/)) $menu.className += ' hidden'; }; } function toggleMenu(e) { if (moveStatus) { moveStatus = false; return; } if ($menu.className.match(/hidden/)) { if (lastPathName !== location.pathname) generateMdMenu(true); const winWidth = document.documentElement.clientWidth, winHeight = document.documentElement.clientHeight; const x = e.clientX, y = e.clientY; $menu.className = `${winWidth / 2 - x > 0 ? 'le-md-right' : 'le-md-left'} ${winHeight / 2 - y > 0 ? 'le-md-bottom' : 'le-md-top'}`; } else { $menu.className += ' hidden'; } } function generateMdMenu(flag) { lastPathName = location.pathname; let $content, list = []; const host = location.host; if (host === 'github.com') { const $parent = document.getElementById('readme') || document.getElementById('wiki-body'); $content = $parent?.getElementsByClassName('markdown-body')[0]; titleHeight = ($parent?.parentElement?.getElementsByClassName('js-sticky')[0]?.offsetHeight || 0) + 2; !$menu && window.addEventListener('pjax:complete', generateMdMenu); } else if (host === 'gitee.com') { $content = document.getElementById('tree-content-holder')?.getElementsByClassName('markdown-body')[0]; } else if (host === 'www.npmjs.com') { $content = document.getElementById('readme'); } else { $content = detectMdContent(); } const $children = $content?.children || []; for (let $dom of $children) { const tag = $dom.tagName; if (tag.length === 2 && tag.startsWith('H') && !isNaN(+tag[1])) { const value = $dom.innerText.trim(); const $a = $dom.getElementsByTagName('a')[0]; if ($a) list.push({ type: +tag[1], value, href: $a.getAttribute('href'), offsetTop: getOffsetTop($a) }); } } if ($menu) $menu.innerHTML = ''; else createMdDom(); if (!flag) $menu.className = 'hidden'; if (list.length) { list.forEach(i => { const li = document.createElement('li'); li.setAttribute('data-offsetTop', i.offsetTop); const a = document.createElement('a'); a.href = i.href; a.title = i.value; a.style = `font-size:${1.3 - i.type * 0.1}em;margin-left:${i.type - 1}em;border-left:0.5em groove hsla(200,80%,${45 + i.type * 10}%,0.8);`; a.innerText = i.value; li.appendChild(a); $menu.appendChild(li); }); $button.className = 'le-md-btn'; } else { $button.className = 'le-md-btn le-md-btn-hidden'; } initScrollHighlight(); } function detectMdContent() { let tmp = []; for (let i = 1; i < 7; i++) { const list = document.body.getElementsByTagName(`h${i}`); for (let j of list) { const parent = j.parentElement; const item = tmp.find(k => k.ele.isEqualNode(parent)); if (item) item.count++; else tmp.push({ ele: parent, count: 1 }); } } return tmp.sort((a, b) => b.count - a.count)[0]?.ele || null; } function getOffsetTop($dom, val = 0) { return $dom ? getOffsetTop($dom.offsetParent, ($dom.offsetTop || 0) + val) : val; } function initScrollHighlight() { const update = debounce(() => { const scrollTop = (document.documentElement.scrollTop || document.body.scrollTop || 0) + titleHeight; const offsetHeight = document.documentElement.clientHeight || document.body.clientHeight || 0; const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight || 0; if ($menu) { Array.from($menu.children).forEach((li, i) => { const val = +li.getAttribute('data-offsetTop'); const nextVal = $menu.children[i + 1] ? +$menu.children[i + 1].getAttribute('data-offsetTop') : scrollHeight; li.className = ''; if (scrollTop <= val && val <= offsetHeight + scrollTop) li.className = 'le-md-title-active'; if (scrollTop >= val && nextVal > scrollTop) li.className = 'le-md-title-active le-md-title-active-first'; }); } }, 500); window.onscroll = window.onscroll ? function() { window.onscroll.call(this); update(); } : update; } function debounce(func, time) { let timeId; return function() { clearTimeout(timeId); timeId = setTimeout(() => func.apply(this, arguments), time); }; } // =========================================================================== // === 功能3:固定页面头部 === // =========================================================================== // 固定 GitHub 页面头部 function fixPageHeader() { // 使用更健壮的选择器定位 GitHub 头部(兼容不同页面) const header = document.querySelector('.AppHeader, header[role="banner"]'); if (!header) { log('未找到页面头部元素'); return; } // 注入固定样式的 CSS const css = document.createElement('style'); css.id = 'fixed-header-style'; // 添加 ID 以便后续更新 css.innerHTML = ` .AppHeader, header[role="banner"] { position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; z-index: 10000 !important; /* 高优先级,避免被覆盖 */ background-color: #24292e !important; /* GitHub 默认头部背景色 */ box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* 添加阴影,提升视觉效果 */ } body { padding-top: ${header.offsetHeight}px !important; /* 根据头部高度调整内容偏移 */ } `; document.head.appendChild(css); // 监听 PJAX 事件,确保动态加载后更新样式 window.addEventListener('pjax:complete', () => { const newHeader = document.querySelector('.AppHeader, header[role="banner"]'); if (newHeader) { document.body.style.paddingTop = `${newHeader.offsetHeight}px`; } }); // 使用 MutationObserver 监听 DOM 变化,确保头部始终固定 const observer = new MutationObserver(() => { const currentHeader = document.querySelector('.AppHeader, header[role="banner"]'); if (currentHeader && !currentHeader.style.position) { document.body.style.paddingTop = `${currentHeader.offsetHeight}px`; } }); observer.observe(document.body, { childList: true, subtree: true }); } // =========================================================================== // === 初始化脚本 === // =========================================================================== document.onreadystatechange = () => { if (document.readyState === 'complete') { generateMdMenu(); // 初始化 MD 目录 fixPageHeader(); // 初始化固定页面头部 log('已加载'); } }; })();