Greasy Fork is available in English.
智能目录:PC端悬浮拖拽(不记录位置),移动端右侧呼出(自动收起),支持窗口尺寸实时切换与平滑滚动。
// ==UserScript==
// @name nodeseek和deepflood智能目录
// @namespace https://github.com/renshengyoumeng
// @version 1.10
// @description 智能目录:PC端悬浮拖拽(不记录位置),移动端右侧呼出(自动收起),支持窗口尺寸实时切换与平滑滚动。
// @match https://www.nodeseek.com/post-*-1
// @match https://www.deepflood.com/post-*-1
// @grant none
// @author renshengyoumeng
// @author2 yzcjd, chatgpt5
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ===== 通用平滑滚动函数 =====
const easeInOutCubic = t => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2);
const smoothScrollTo = (targetY, duration = 600) => {
const startY = window.scrollY || window.pageYOffset;
const distance = targetY - startY;
let startTime = null;
function step(currentTime) {
if (!startTime) startTime = currentTime;
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const eased = easeInOutCubic(progress);
window.scrollTo(0, startY + distance * eased);
if (elapsed < duration) requestAnimationFrame(step);
}
requestAnimationFrame(step);
};
// ===== 基础过滤逻辑 =====
const hostname = location.hostname;
const exclude = ['cloudflare', 'captcha', 'challenge', 'login', 'auth', 'verify'];
if (exclude.some(k => hostname.includes(k) || location.pathname.includes(k))) return;
// ===== 创建目录结构 =====
const toc = document.createElement('div');
toc.id = 'smart-toc';
toc.innerHTML = `
<div id="toc-header" style="background:#ccc;padding:5px;cursor:move;">📑 目录</div>
<div id="toc-list" style="margin:0;padding:0 6px;overflow:auto;max-height:calc(80vh - 30px);"></div>
`;
document.body.appendChild(toc);
const tocList = toc.querySelector('#toc-list');
// ===== 提取标题 =====
const ignore = ['header','footer','nav','aside','.navbar','.sidebar','.avatar','.logo','.banner','.desc','.tabs','.player','.playlist','.switch','.user','.meta'];
const isVisible = el => !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
const isValidHeading = el => isVisible(el) && !ignore.some(sel => el.closest(sel));
let headings = [
document.querySelector('.post-title h1 a.post-title-link'),
...document.querySelector('.post-content')?.querySelectorAll('main h1,h2,h3,h4,h5,h6, article h1,h2,h3,h4,h5,h6, .content h1,h2,h3,h4,h5,h6') || []
].filter(Boolean).filter(isValidHeading);
if (headings.length <= 1) { toc.remove(); return; }
const longest = Math.max(...headings.map(el => el.textContent.trim().length));
if (longest >= 20) toc.style.width = '375px';
headings.forEach((el, i) => { if (!el.id) el.id = 'smart-toc-' + i; });
const getLevel = tag => parseInt(tag.replace('H', ''));
headings.forEach(el => {
const level = Math.min(getLevel(el.tagName), 3);
const a = document.createElement('a');
a.href = `#${el.id}`;
a.textContent = el.textContent.trim();
const indent = level === 1 ? 0 : (level - 1) * 1.5;
a.style.cssText = `display:block;padding:3px 10px;padding-left:${indent}em;color:inherit;text-decoration:none;user-select:none;`;
tocList.appendChild(a);
});
// ===== 点击目录跳转 =====
tocList.addEventListener('click', e => {
if (e.target.tagName.toLowerCase() === 'a') {
const id = e.target.getAttribute('href').slice(1);
const target = document.getElementById(id);
if (target) {
e.preventDefault();
const rect = target.getBoundingClientRect();
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
smoothScrollTo(scrollTop + rect.top - 60, 600);
}
}
});
// ===== PC模式 =====
function setupDesktopMode() {
cleanupMode();
toc.style.cssText = `
position:fixed; top:50px; right:50px; width:250px; max-height:80vh;
background:#fff; border:1px solid #ccc; border-radius:8px;
box-shadow:0 2px 8px rgba(0,0,0,0.15); overflow:hidden;
z-index:99999; font-family:sans-serif;
`;
const header = toc.querySelector('#toc-header');
header.style.cursor = 'move';
// 启用简单拖拽(不记忆)
header.addEventListener('mousedown', e => {
e.preventDefault();
const rect = toc.getBoundingClientRect();
const offsetX = e.clientX - rect.left;
const offsetY = e.clientY - rect.top;
const move = e => {
toc.style.left = `${e.clientX - offsetX}px`;
toc.style.top = `${e.clientY - offsetY}px`;
};
const stop = () => {
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', stop);
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', stop);
});
}
// ===== 移动端模式 =====
let toggleBtn = null, overlay = null;
function setupMobileMode() {
cleanupMode();
toc.style.cssText = `
position: fixed;
top: 0;
right: -80%;
width: 75%;
height: 100%;
background: #fff;
box-shadow: -2px 0 8px rgba(0,0,0,0.2);
z-index: 99999;
border-radius: 0;
transition: right 0.4s ease;
overflow-y: auto;
visibility: hidden;
`;
toggleBtn = document.createElement('button');
toggleBtn.textContent = '📑 目录';
toggleBtn.style.cssText = `
position: fixed;
top: 65px;
right: 15px;
z-index: 100000;
background: #0078d7;
color: #fff;
border: none;
border-radius: 6px;
padding: 6px 10px;
font-size: 14px;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
`;
overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.4);
z-index: 99998;
display: none;
`;
document.body.appendChild(toggleBtn);
document.body.appendChild(overlay);
let isOpen = false;
const openTOC = () => {
toc.style.visibility = 'visible';
toc.style.right = '0';
overlay.style.display = 'block';
isOpen = true;
};
const closeTOC = () => {
toc.style.right = '-80%';
overlay.style.display = 'none';
isOpen = false;
};
toggleBtn.addEventListener('click', () => isOpen ? closeTOC() : openTOC());
overlay.addEventListener('click', closeTOC);
tocList.addEventListener('click', e => {
if (e.target.tagName.toLowerCase() === 'a') closeTOC();
});
toc.querySelector('#toc-header').style.cursor = 'default';
}
// ===== 清理上一个模式 =====
function cleanupMode() {
toc.style.left = '';
toc.style.top = '';
toc.style.right = '';
if (toggleBtn) { toggleBtn.remove(); toggleBtn = null; }
if (overlay) { overlay.remove(); overlay = null; }
}
// ===== 初始化 & 自适应切换 =====
let isMobileMode = window.innerWidth < 768;
function updateMode() {
const nowMobile = window.innerWidth < 768;
if (nowMobile !== isMobileMode) {
isMobileMode = nowMobile;
nowMobile ? setupMobileMode() : setupDesktopMode();
}
}
// 初始化
isMobileMode ? setupMobileMode() : setupDesktopMode();
// 窗口变化监听(防抖)
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(updateMode, 300);
});
console.log('✅ nodeseek/deepflood 智能目录 v1.10 自适应切换版 启用成功');
})();