NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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 // @updateURL https://openuserjs.org/meta/omomo/AnkiWeb_Deck_Collapse.meta.js // @downloadURL https://openuserjs.org/install/omomo/AnkiWeb_Deck_Collapse.user.js // ==/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(); })();