【古诗文网】自动展开注释、译文和赏析

自动展开古诗文网的注释、译文和赏析内容,性能优化版本

// ==UserScript==
// @name         【古诗文网】自动展开注释、译文和赏析
// @namespace    https://github.com/realSilasYang
// @version         2025-1-2
// @description    自动展开古诗文网的注释、译文和赏析内容,性能优化版本
// @author          阳熙来
// @match          https://www.gushiwen.cn/*
// @grant            none
// @icon             https://www.google.com/s2/favicons?domain=https://www.gushiwen.cn
// @license         GNU GPLv3
// ==/UserScript==

(function () {
    'use strict';

    // 配置项
    const CONFIG = {
        CLICK_DELAY: 50,        // 每个按钮点击的延迟时间(毫秒)
        LOAD_MARGIN: '100px',   // IntersectionObserver 提前加载的距离(像素)
        DEBUG: false            // 是否开启调试模式
    };

    // 按钮与诗词容器的选择器
    const SELECTORS = {
        POEM_CONTAINER: '.sons .cont', // 用于标识诗词内容区域的选择器
        BUTTONS: {
            NOTE: 'img[src="https://ziyuan.guwendao.net/siteimg/zhu-pic.png"]',         // 注释按钮
            TRANSLATION: 'img[src="https://ziyuan.guwendao.net/siteimg/yi-pic.png"]',  // 译文按钮
            ANALYSIS: 'img[src="https://ziyuan.guwendao.net/siteimg/shang-pic.png"]'   // 赏析按钮
        }
    };

    /**
     * 日志工具,用于输出调试信息
     */
    const logger = {
        log: (...args) => CONFIG.DEBUG && console.log('[古诗文展开]', ...args),
        error: (...args) => CONFIG.DEBUG && console.error('[古诗文展开]', ...args)
    };

    /**
     * 检查元素是否在视口中
     * @param {Element} element - 要检查的 HTML 元素
     * @returns {boolean} - 返回布尔值,表示元素是否在视口中
     */
    function isInViewport(element) {
        const rect = element.getBoundingClientRect();
        return (
            rect.top >= 0 &&
            rect.left >= 0 &&
            rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
            rect.right <= (window.innerWidth || document.documentElement.clientWidth)
        );
    }

    /**
     * 快速点击诗词容器内的所有按钮
     * @param {Element} container - 诗词容器元素
     */
    function clickButtonsInContainer(container) {
        try {
            // 如果容器已被处理过,则直接返回
            if (!container || container.hasAttribute('processed')) {
                return;
            }

            const buttons = [];
            // 遍历每种按钮的选择器,收集未点击的按钮
            Object.entries(SELECTORS.BUTTONS).forEach(([type, selector]) => {
                const button = container.querySelector(selector);
                if (button && !button.hasAttribute('clicked') && button.style.display !== 'none') {
                    buttons.push({ type, element: button });
                }
            });

            // 按顺序点击按钮,并设置点击的时间间隔
            if (buttons.length) {
                buttons.forEach((button, index) => {
                    setTimeout(() => {
                        try {
                            button.element.setAttribute('clicked', 'true'); // 标记按钮已被点击
                            button.element.click(); // 执行点击操作
                            logger.log(`Clicked ${button.type} button`);
                        } catch (err) {
                            logger.error(`Error clicking ${button.type} button:`, err);
                        }
                    }, index * CONFIG.CLICK_DELAY);
                });
            }

            // 标记容器已处理
            container.setAttribute('processed', 'true');
        } catch (err) {
            logger.error('Error in clickButtonsInContainer:', err);
        }
    }

    /**
     * 使用 IntersectionObserver 优化性能,按需加载诗词容器
     */
    function setupIntersectionObserver() {
        try {
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        clickButtonsInContainer(entry.target); // 当进入视口时,处理该容器
                        observer.unobserve(entry.target);     // 停止观察该容器
                    }
                });
            }, {
                rootMargin: CONFIG.LOAD_MARGIN, // 设置提前加载的距离
                threshold: 0                   // 当容器完全进入视口时触发
            });

            // 遍历所有未处理的诗词容器,并开始观察
            document.querySelectorAll(`${SELECTORS.POEM_CONTAINER}:not([processed])`).forEach(container => {
                observer.observe(container);
            });
        } catch (err) {
            logger.error('Error in setupIntersectionObserver:', err);
            processVisibleContainers(); // 降级处理:直接处理当前视口中的容器
        }
    }

    /**
     * 降级处理:直接处理当前视口中的容器
     */
    function processVisibleContainers() {
        try {
            const containers = document.querySelectorAll(`${SELECTORS.POEM_CONTAINER}:not([processed])`);
            containers.forEach(container => {
                if (isInViewport(container)) {
                    clickButtonsInContainer(container); // 处理视口中的容器
                }
            });
        } catch (err) {
            logger.error('Error in processVisibleContainers:', err);
        }
    }

    /**
     * 监听 DOM 中新增的内容,用于动态加载的场景
     */
    function observeNewContent() {
        try {
            const observer = new MutationObserver((mutations) => {
                let shouldProcess = false;

                // 遍历所有变动记录,判断是否有新增的诗词容器
                for (const mutation of mutations) {
                    if (mutation.addedNodes.length) {
                        const hasNewPoems = Array.from(mutation.addedNodes).some(node =>
                            node.nodeType === Node.ELEMENT_NODE &&
                            (node.matches(SELECTORS.POEM_CONTAINER) ||
                                node.querySelector(SELECTORS.POEM_CONTAINER))
                        );

                        if (hasNewPoems) {
                            shouldProcess = true;
                            break;
                        }
                    }
                }

                if (shouldProcess) {
                    setupIntersectionObserver(); // 如果有新增内容,则重新设置观察器
                }
            });

            // 监听整个文档的子节点变动
            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        } catch (err) {
            logger.error('Error in observeNewContent:', err);
        }
    }

    /**
     * 防抖函数:限制高频调用,延迟执行
     * @param {Function} func - 要防抖的函数
     * @param {number} wait - 延迟时间(毫秒)
     * @returns {Function}
     */
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    /**
     * 初始化脚本
     */
    function init() {
        try {
            logger.log('Initializing...');
            setupIntersectionObserver(); // 设置 IntersectionObserver
            observeNewContent();         // 监听新增内容
        } catch (err) {
            logger.error('Error in init:', err);
        }
    }

    // 使用防抖包装初始化函数
    const debouncedInit = debounce(init, 100);

    // 添加页面事件监听器
    window.addEventListener('load', debouncedInit);
    window.addEventListener('popstate', debouncedInit);
    window.addEventListener('scroll', debounce(processVisibleContainers, 100));

    // 重写 history.pushState 方法,监控前端路由变化
    const originalPushState = history.pushState;
    history.pushState = function () {
        originalPushState.apply(this, arguments);
        debouncedInit();
    };

    // 立即初始化
    debouncedInit();

    // 如果开启了调试模式,导出调试接口
    if (CONFIG.DEBUG) {
        window._gushiwen_debug = {
            processVisibleContainers,
            clickButtonsInContainer,
            CONFIG,
            SELECTORS
        };
    }
})();