Greasy Fork

智能目录(优化版)

智能提取网页标题生成目录,支持拖拽记忆位置、双击收起、重置位置等。自动排除验证码和无目录页面。

// ==UserScript==
// @name         智能目录(优化版)
// @namespace    https://greasyfork.org/users/1171320
// @version      1.0
// @description  智能提取网页标题生成目录,支持拖拽记忆位置、双击收起、重置位置等。自动排除验证码和无目录页面。
// @author       yzcjd
// @author2     ChatGPT4 辅助
// @match        *://*/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
  'use strict';

  const excludePatterns = [
    'cloudflare', 'challenge', 'captcha', 'recaptcha',
    'accounts.google.com', 'facebook.com/login', 'twitter.com/login',
    'auth', 'verify', 'login', 'signin', 'logon'
  ];

  const hostname = location.hostname;
  if (excludePatterns.some(pattern => hostname.includes(pattern))) {
    console.log('智能目录:已跳过本页面(检测到可能是验证码/登录页)');
    return;
  }

  function $(selector, parent = document) { return parent.querySelector(selector); }
  function $all(selector, parent = document) { return [...parent.querySelectorAll(selector)]; }
  function savePosition(x, y) {
    const key = 'SmartTOC:position:' + hostname;
    localStorage.setItem(key, JSON.stringify({ x, y }));
  }
  function loadPosition() {
    const key = 'SmartTOC:position:' + hostname;
    const pos = localStorage.getItem(key);
    if (pos) { try { return JSON.parse(pos); } catch (e) {} }
    return null;
  }
  function resetPosition() {
    const key = 'SmartTOC:position:' + hostname;
    localStorage.removeItem(key);
    location.reload();
  }

  const toc = document.createElement('div');
  toc.id = 'smart-toc';
  toc.style.cssText = `
    position: fixed;
    top: 50px;
    right: 50px;
    width: 250px;
    max-height: 80vh;
    overflow: auto;
    background: #fff;
    color: #333;
    font-size: 14px;
    font-family: sans-serif;
    border: 1px solid #ccc;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.15);
    z-index: 99999;
    transition: max-height 0.3s, width 0.3s;
  `;
  toc.innerHTML = '<div id="smart-toc-header" style="background:#ccc;color:#000;padding:5px;cursor:move;user-select:none;">📑 目录 <button id="reset-btn" style="float:right;background:#ccc;border:none;color:black;cursor:pointer;">重置</button></div><ul id="smart-toc-list" style="margin:0;padding:10px;list-style:none;"></ul>';
  document.body.appendChild(toc);

  const tocList = $('#smart-toc-list');
  const resetBtn = $('#reset-btn');

  const headings = $all('h1, h2, h3, h4, h5, h6');
  if (!headings.length) {
    console.log('智能目录:未找到标题元素,退出。');
    toc.remove();
    return;
  }

  headings.forEach((el, idx) => {
    if (!el.id) { el.id = 'smart-toc-' + idx; }
    const li = document.createElement('li');
    li.innerHTML = `<a href="#${el.id}" style="display:block;padding:3px 0;color:inherit;text-decoration:none;">${el.innerText.trim()}</a>`;
    tocList.appendChild(li);
  });

  (function enableDrag() {
    const header = $('#smart-toc-header');
    let offsetX, offsetY, dragging = false;
    header.addEventListener('mousedown', function(e) {
      dragging = true;
      offsetX = e.clientX - toc.offsetLeft;
      offsetY = e.clientY - toc.offsetTop;
      e.preventDefault();
    });
    document.addEventListener('mousemove', function(e) {
      if (!dragging) return;
      let x = e.clientX - offsetX;
      let y = e.clientY - offsetY;
      x = Math.max(0, Math.min(window.innerWidth - toc.offsetWidth, x));
      y = Math.max(0, Math.min(window.innerHeight - toc.offsetHeight, y));
      toc.style.left = x + 'px';
      toc.style.top = y + 'px';
    });
    document.addEventListener('mouseup', function() {
      if (dragging) {
        savePosition(toc.offsetLeft, toc.offsetTop);
        dragging = false;
      }
    });
  })();

  (function restorePosition() {
    const pos = loadPosition();
    if (pos) {
      toc.style.left = pos.x + 'px';
      toc.style.top = pos.y + 'px';
    }
  })();

  resetBtn.addEventListener('click', resetPosition);

  toc.addEventListener('dblclick', function() {
    if (tocList.style.display === 'none') {
      tocList.style.display = '';
      toc.style.height = '';
    } else {
      tocList.style.display = 'none';
      toc.style.height = 'auto';
    }
  });

  console.log('智能目录:已启用!');
})();