AnkiWeb Deck Collapse

Add collapse/expand functionality to AnkiWeb deck pages with hierarchical indentation.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         AnkiWeb Deck Collapse
// @name:zh-CN   AnkiWeb Deck 折叠功能
// @namespace    https://github.com/deepseek-ai/DeepSeek-V3
// @version      1.0
// @description  Add collapse/expand functionality to AnkiWeb deck pages with hierarchical indentation.
// @description:zh-CN 在 AnkiWeb 的 deck 页面添加折叠/展开功能,并支持层级缩进。
// @author       DeepSeek-V3 | ovo
// @match        https://ankiweb.net/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    // 初始化函数
    function initializeDeckCollapse() {
        // 只在/decks路径下运行
        if (window.location.pathname !== '/decks') {
            return;
        }

        // 获取所有的deck行
        const deckRows = document.querySelectorAll('.row.light-bottom-border');

        // 用于存储每个deck的层级关系和子deck
        const deckMap = new Map();

        // 遍历每个deck行,构建层级关系
        deckRows.forEach((row, index) => {
            const button = row.querySelector('button');
            const textContent = button.textContent;

            // 计算空格数量来确定层级
            const indentLevel = (textContent.match(/^\s*/) || [''])[0].length;

            // 存储当前deck的层级和子deck
            deckMap.set(row, {
                indentLevel,
                children: [],
                isCollapsed: true // 默认收起
            });

            // 找到当前deck的父deck
            if (indentLevel > 0) {
                for (let i = index - 1; i >= 0; i--) {
                    const parentRow = deckRows[i];
                    const parentIndentLevel = deckMap.get(parentRow).indentLevel;

                    // 如果找到父deck
                    if (parentIndentLevel < indentLevel) {
                        deckMap.get(parentRow).children.push(row);
                        break;
                    }
                }
            }
        });

        // 遍历每个deck行,添加展开/收起按钮和缩进
        deckRows.forEach(row => {
            const { indentLevel, children } = deckMap.get(row);
            const button = row.querySelector('button');
            const deckName = button.textContent.trim();

            // 修改button的文本为deckName
            button.textContent = deckName;

            // 计算margin-left
            const marginLeft = Math.max((indentLevel - 4), 0) + 'em';

            // 如果当前deck有子deck,则添加展开/收起按钮
            if (children.length > 0) {
                // 创建展开/收起按钮
                const toggleButton = document.createElement('button');
                toggleButton.textContent = '+'; // 默认收起
                toggleButton.style.background = 'none';
                toggleButton.style.border = 'none';
                toggleButton.style.cursor = 'pointer';
                toggleButton.style.marginLeft = marginLeft; // 设置缩进

                // 检查localStorage中是否存储了该deck的展开状态
                const isCollapsed = localStorage.getItem(`deckCollapsed_${deckName}`) !== 'false';

                // 如果存储的状态是展开,则展开直接子deck
                if (!isCollapsed) {
                    toggleButton.textContent = '-';
                    toggleDirectChildren(row, false); // 展开直接子deck
                } else {
                    toggleDirectChildren(row, true); // 收起直接子deck
                }

                // 添加点击事件来切换展开/收起状态
                toggleButton.addEventListener('click', () => {
                    const isCollapsed = toggleButton.textContent === '+';
                    if (isCollapsed) {
                        toggleButton.textContent = '-';
                        toggleDirectChildren(row, false); // 展开直接子deck
                        localStorage.setItem(`deckCollapsed_${deckName}`, 'false');
                    } else {
                        toggleButton.textContent = '+';
                        toggleDirectChildren(row, true); // 收起直接子deck
                        localStorage.setItem(`deckCollapsed_${deckName}`, 'true');
                    }
                });

                // 将按钮插入到button之前
                button.parentNode.insertBefore(toggleButton, button);
            } else {
                // 最低层级deck,只设置margin-left
                button.style.marginLeft = marginLeft;
            }
        });

        // 切换直接子deck的显示状态
        function toggleDirectChildren(parentRow, isCollapsed) {
            const children = deckMap.get(parentRow).children;
            children.forEach(child => {
                child.style.display = isCollapsed ? 'none' : '';
            });
        }
    }

    // 监听点击事件,检测导航变化
    function observeNavigation() {
        document.body.addEventListener('click', (event) => {
            const target = event.target;
            if (target.tagName === 'A' && target.hostname === window.location.hostname) {
                // 如果点击的是当前站点的链接,重新初始化脚本
                reinitializeScript();
            }
        });
    }

    // 重新初始化脚本
    function reinitializeScript() {
        // 重新观察 DOM 变化
        observer.disconnect();
        observer.observe(document.body, {
            childList: true, // 观察子节点的变化
            subtree: true // 观察所有后代节点
        });
    }

    // 监听 DOM 变化
    const observer = new MutationObserver((mutationsList, observer) => {
        // 检查是否有动态内容加载
        const deckRows = document.querySelectorAll('.row.light-bottom-border');
        if (deckRows.length > 0) {
            // 停止观察,避免重复运行
            observer.disconnect();

            // 执行主逻辑
            initializeDeckCollapse();
        }
    });

    // 开始观察整个文档
    observer.observe(document.body, {
        childList: true, // 观察子节点的变化
        subtree: true // 观察所有后代节点
    });

    // 监听导航变化
    observeNavigation();
})();