中文维基百科移动版智能折叠章节

在zh.m.wikipedia.org自动折叠参考链接、外部链接等章节,需在设置中启用“展开所有章节”

// ==UserScript==
// @name         中文维基百科移动版智能折叠章节
// @namespace    http://tampermonkey.net/
// @version      1.0
// @license MIT
// @description  在zh.m.wikipedia.org自动折叠参考链接、外部链接等章节,需在设置中启用“展开所有章节”
// @author       Claude
// @match        https://zh.m.wikipedia.org/*
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // 要折叠的章节标题
    const TARGET_SECTIONS = ['参看', '参见', '参考', '参考链接', '参考资料', '参考书目', '外部链接', '外部连接', '参考文献', '资料来源', '相关条目', '相关作品', '关联项目', '延伸阅读', '拓展阅读', '注释', '脚注', '著名人物'];
    
    // 存储键名
    const STORAGE_KEY = 'wiki_sections_collapsed';
    
    // 获取当前折叠状态
    let isCollapsed = GM_getValue(STORAGE_KEY, true);
    
    // 存储已处理的章节
    let processedSections = new Set();

    // 日志函数
    function log(message) {
        console.log('[维基章节折叠] ' + message);
    }

    // 检查文本是否匹配目标章节
    function isTargetSection(text) {
        const cleanText = text.replace(/\[编辑\]|\[編輯\]/g, '').trim();
        return TARGET_SECTIONS.some(section => 
            cleanText === section || 
            cleanText.includes(section) ||
            cleanText.match(new RegExp(section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'))
        );
    }

    // 查找章节对应的切换按钮和内容区域
    function findSectionElements(heading) {
        const result = {
            toggleButton: null,
            contentArea: null,
            sectionContainer: null
        };

        // 查找切换按钮的多种可能位置
        const buttonSelectors = [
            '.mw-ui-icon',
            '.toggle-list__toggle', 
            '[class*="toggle"]',
            '[class*="collapse"]',
            '[class*="expand"]',
            '.section-heading .mw-ui-icon',
            '.collapsible-heading .mw-ui-icon'
        ];

        // 在标题及其相关容器中查找按钮
        const searchContainers = [
            heading,
            heading.parentElement,
            heading.closest('.section-heading'),
            heading.closest('.collapsible-heading'),
            heading.closest('section'),
            heading.closest('[class*="section"]')
        ];

        for (let container of searchContainers) {
            if (!container) continue;
            
            for (let selector of buttonSelectors) {
                const button = container.querySelector(selector);
                if (button) {
                    result.toggleButton = button;
                    result.sectionContainer = container;
                    break;
                }
            }
            if (result.toggleButton) break;
        }

        // 查找内容区域
        if (result.sectionContainer) {
            // 在章节容器中查找内容区域
            const contentSelectors = [
                '.section-content',
                '.mw-parser-output',
                '.content',
                '[class*="content"]',
                '[id^="content-collapsible-block"]'
            ];

            for (let selector of contentSelectors) {
                const content = result.sectionContainer.querySelector(selector);
                if (content) {
                    result.contentArea = content;
                    break;
                }
            }
        }

        // 如果在容器中没找到,尝试查找后续的兄弟元素
        if (!result.contentArea) {
            let nextElement = heading.nextElementSibling;
            let scanCount = 0;
            
            while (nextElement && scanCount < 5) {
                // 如果遇到下一个标题就停止
                if (nextElement.tagName && nextElement.tagName.match(/^H[1-6]$/)) {
                    break;
                }
                
                // 检查是否是内容区域
                if (nextElement.id && nextElement.id.match(/^content-collapsible-block-\d+$/)) {
                    result.contentArea = nextElement;
                    break;
                }
                
                // 或者检查是否包含大量文本内容
                if (nextElement.textContent && nextElement.textContent.length > 50) {
                    result.contentArea = nextElement;
                    break;
                }
                
                nextElement = nextElement.nextElementSibling;
                scanCount++;
            }
        }

        return result;
    }

    // 尝试折叠章节的多种方法
    function collapseSection(heading, elements) {
        const headingText = heading.textContent.replace(/\[编辑\]|\[編輯\]/g, '').trim();
        let success = false;

        // 检查章节是否已经被折叠(简化检查,移除脚本标记检查)
        if (elements.contentArea && elements.contentArea.offsetHeight === 0) {
            log(`章节已折叠,跳过: ${headingText}`);
            return true;
        }

        // 方法1: 点击切换按钮
        if (elements.toggleButton) {
            try {
                // 检查按钮状态
                const isExpanded = !elements.toggleButton.classList.contains('mf-icon-expand') &&
                                 !elements.toggleButton.classList.contains('collapsed');
                
                if (isExpanded) {
                    log(`尝试通过按钮折叠章节: ${headingText}`);
                    
                    // 添加处理标记,防止重复处理
                    elements.toggleButton.setAttribute('data-processing', 'true');
                    
                    elements.toggleButton.click();
                    success = true;
                    
                    // 延迟移除标记
                    setTimeout(() => {
                        elements.toggleButton.removeAttribute('data-processing');
                    }, 1000);
                }
            } catch (e) {
                log(`按钮点击失败: ${e.message}`);
            }
        }

        // 方法2: 点击整个标题区域
        if (!success && elements.sectionContainer && !elements.sectionContainer.getAttribute('data-processing')) {
            try {
                log(`尝试通过点击标题折叠章节: ${headingText}`);
                
                elements.sectionContainer.setAttribute('data-processing', 'true');
                
                // 创建点击事件
                const clickEvent = new MouseEvent('click', {
                    bubbles: true,
                    cancelable: true,
                    view: window
                });
                
                elements.sectionContainer.dispatchEvent(clickEvent);
                success = true;
                
                setTimeout(() => {
                    elements.sectionContainer.removeAttribute('data-processing');
                }, 1000);
            } catch (e) {
                log(`标题点击失败: ${e.message}`);
            }
        }

        return success;
    }

    // 展开章节(简化版,移除直接操作方法)
    function expandSection(heading, elements) {
        const headingText = heading.textContent.replace(/\[编辑\]|\[編輯\]/g, '').trim();
        log(`章节展开请求: ${headingText} (使用原生维基机制)`);
    }

    // 处理所有目标章节
    function processSections() {
        let processedCount = 0;
        
        // 查找所有可能的标题元素
        const headingSelectors = [
            'h1, h2, h3, h4, h5, h6',
            '.mw-headline',
            '.section-heading',
            '[class*="heading"]'
        ];
        
        const allHeadings = new Set();
        
        headingSelectors.forEach(selector => {
            document.querySelectorAll(selector).forEach(heading => {
                allHeadings.add(heading);
            });
        });
        
        allHeadings.forEach(heading => {
            const headingText = heading.textContent || heading.innerText || '';
            
            if (isTargetSection(headingText)) {
                const sectionId = heading.id || `section-${heading.textContent.slice(0, 10)}`;
                
                if (processedSections.has(sectionId)) {
                    return; // 已处理过的章节跳过
                }
                
                log(`发现目标章节: "${headingText.trim()}"`);
                
                const elements = findSectionElements(heading);
                
                if (isCollapsed) {
                    if (collapseSection(heading, elements)) {
                        processedSections.add(sectionId);
                        processedCount++;
                    }
                } else {
                    expandSection(heading, elements);
                    processedSections.delete(sectionId);
                }
            }
        });
        
        log(`本次处理了 ${processedCount} 个章节`);
        return processedCount;
    }

    // 防抖处理函数
    let processTimeout = null;
    let isProcessing = false;
    
    function debouncedProcessSections() {
        if (isProcessing) {
            log('正在处理中,跳过本次触发');
            return;
        }
        
        clearTimeout(processTimeout);
        processTimeout = setTimeout(() => {
            if (isCollapsed && !isProcessing) {
                isProcessing = true;
                log('开始防抖处理章节');
                processSections();
                setTimeout(() => {
                    isProcessing = false;
                    log('防抖处理完成');
                }, 2000);
            }
        }, 1500);
    }

    // 监听DOM变化
    function setupMutationObserver() {
        const observer = new MutationObserver((mutations) => {
            let shouldProcess = false;
            let hasStyleChanges = false;
            
            mutations.forEach((mutation) => {
                // 忽略脚本自身造成的样式变化
                if (mutation.type === 'attributes') {
                    if (mutation.attributeName === 'style' || 
                        mutation.attributeName === 'data-collapsed-by-script' ||
                        mutation.attributeName === 'class') {
                        hasStyleChanges = true;
                        return;
                    }
                }
                
                // 只处理真正的内容添加
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            // 忽略脚本添加的样式元素
                            if (node.tagName === 'STYLE' && node.id === 'wiki-hide-blocks-style') {
                                return;
                            }
                            
                            // 检查是否添加了新的标题或章节内容
                            if (node.tagName && node.tagName.match(/^H[1-6]$/)) {
                                shouldProcess = true;
                            } else if (node.querySelector && 
                                     (node.querySelector('h1, h2, h3, h4, h5, h6') ||
                                      node.querySelector('[class*="section"]') ||
                                      node.querySelector('[id*="content-collapsible"]'))) {
                                shouldProcess = true;
                            }
                        }
                    });
                }
            });
            
            // 如果只是样式变化,不触发处理
            if (hasStyleChanges && !shouldProcess) {
                return;
            }
            
            if (shouldProcess && isCollapsed && !isProcessing) {
                log('DOM变化检测到新内容,准备处理');
                debouncedProcessSections();
            }
        });
        
        observer.observe(document.documentElement, {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ['style', 'class']
        });
        
        log('DOM变化监听器已启动');
    }

    // 设置URL变化监听
    function setupUrlChangeListener() {
        let lastUrl = location.href;
        
        const urlObserver = new MutationObserver(() => {
            const currentUrl = location.href;
            if (currentUrl !== lastUrl) {
                lastUrl = currentUrl;
                log('检测到URL变化: ' + currentUrl);
                
                // URL变化后清空记录并重新处理
                processedSections.clear();
                setTimeout(() => {
                    if (isCollapsed && !isProcessing) {
                        debouncedProcessSections();
                    }
                }, 1500);
            }
        });
        
        urlObserver.observe(document, { subtree: true, childList: true });
        
        window.addEventListener('popstate', () => {
            processedSections.clear();
            setTimeout(() => {
                if (isCollapsed && !isProcessing) {
                    debouncedProcessSections();
                }
            }, 1000);
        });
    }

    // 初始化函数
    function init() {
        log('脚本初始化开始');
        log(`当前折叠状态: ${isCollapsed}`);
        
        // 设置监听器
        setupMutationObserver();
        setupUrlChangeListener();
        
        // 页面加载完成后处理
        const processWhenReady = () => {
            if (isCollapsed && !isProcessing) {
                isProcessing = true;
                setTimeout(() => {
                    processSections();
                    setTimeout(() => {
                        isProcessing = false;
                    }, 2000);
                }, 500);
            }
        };
        
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                setTimeout(processWhenReady, 1000);
            });
        } else {
            setTimeout(processWhenReady, 500);
        }
        
        // 多次延迟执行,确保处理动态加载的内容
        setTimeout(processWhenReady, 2000);
        setTimeout(processWhenReady, 4000);
        
        log('脚本初始化完成');
    }

    // 启动脚本
    init();
})();