[js] 双击展开 & 展开全部 _release_v1.0

[js 片段] 双击展开&展开全部_release_v1.0

功能介绍

  1. 双击节点展开/折叠 (DOUBLE_SWITCH 控制)
  2. 点击折叠按钮, 如果是从折叠到展开, 则自动展开所有子节点 (UNFOLD_ALL_SWITCH 控制)
/*
### [js片段] 双击展开&展开全部_release_v1.0
#### 功能介绍
1. 双击节点展开/折叠 (DOUBLE_SWITCH 控制)
2. 点击折叠按钮, 如果是从折叠到展开, 则自动展开所有子节点 (UNFOLD_ALL_SWITCH 控制)

*/
(() => {
    /***************************自主配置begin**************************************/
    // 修改配置后, 需要刷新页面(设置->快捷键->刷新), 否则会有问题

    const CONFIG = {
        DOUBLE_SWITCH     : true,  // 是否启用双击展开/折叠
        DOUBLE_INTERVAL   : 200,   // 双击间隔时间 毫秒
        UNFOLD_ALL_SWITCH : true,  // 是否替换原始折叠按钮功能, 改为一键全部展开
        UNFOLD_INTERVAL   : 200,   // 自动展开的点击的间隔, 如果你感觉展开很慢, 可适当调小
                                   // 请注意 如果过于小, 可能会导致文档树渲染异常
    };

    /***************************自主配置end****************************************/

    // 生成唯一ID用于日志标识
    const SESSION_ID = Date.now();

    // 点击类型枚举
    const CLICK_TYPE = {
        INVALID: 0,  // 无效的点击
        NODE   : 1,  // 点击节点
        ARROW  : 2,  // 点击折叠按钮
        BOOK   : 3,  // 点击笔记本
    };

    // 工具函数
    const utils = {
        log(...args) {
            console.log(`[${SESSION_ID}]:`, ...args);
        },

        sleep(ms) {
            return new Promise(resolve => setTimeout(resolve, ms));
        },

        getTagName(element) {
            return element?.tagName?.toLowerCase() || '';
        }
    };

    // 树节点处理类
    class TreeHandler {
        constructor() {
            this.clickQueue = [];    // 点击队列
            this.isProcessing = false; // 是否正在处理队列
            this.doubleClickFlag = false; // 双击标记
            this.lastClickTime = 0;     // 上次点击时间
        }

        // 获取当前元素对应的种类
        getClickType(element) {
            if (!element) return CLICK_TYPE.INVALID;

            let target = element;
            let tagName = utils.getTagName(target);

            // 处理SVG元素
            if (tagName === 'use') {
                target = element.parentElement.parentElement;
            } else if (tagName === 'svg') {
                target = element.parentElement;
            }

            tagName = utils.getTagName(target);
            if (tagName === 'ul') {
                return CLICK_TYPE.INVALID;
            }
            else if (tagName === 'li' && target.getAttribute('data-path') === '/') {
                return CLICK_TYPE.BOOK;
            }
            else if (tagName === 'li') {
                return CLICK_TYPE.NODE;
            }
            else if (tagName === 'span') {
                if (target.classList.contains('b3-list-item__toggle')) {
                    return CLICK_TYPE.ARROW;
                }
                else if (target.classList.contains('b3-list-item__text') &&
                    target.parentElement.getAttribute('data-path') === '/') {
                    return CLICK_TYPE.BOOK;
                }
                else if (target.classList.contains('b3-list-item__text')) {
                    return CLICK_TYPE.NODE;
                }
            }
            return CLICK_TYPE.INVALID;
        }

        // 获取折叠按钮的span元素, 前提: element必须是折叠按钮
        getArrowSpan(element) {
            let target = element;
            let tagName = utils.getTagName(target);

            if (tagName === 'use') {
                target = element.parentElement.parentElement;
            } else if (tagName === 'svg') {
                target = element.parentElement;
            }

            tagName = utils.getTagName(target);
            if (tagName === 'span' && target.classList.contains('b3-list-item__toggle')) {
                return target;
            }
            return null;
        }

        // 点击节点对应的折叠按钮, 前提: element必须是点击的节点
        clickArrowButton(element) {
            const li = utils.getTagName(element) === 'span' ?
                element.parentElement : element;
            if (utils.getTagName(li) !== 'li') return;

            li.querySelector('span.b3-list-item__toggle:not(.fn__hidden)')?.click();
        }

        // 遍历 element下所有直接子节点的折叠按钮, 并点击
        async unfoldChildNodes(element) {
            const arrowSpan = this.getArrowSpan(element);
            if (!arrowSpan) return;

            if (!arrowSpan.querySelector('svg').classList.contains('b3-list-item__arrow--open')) {
                return;
            }

            // 点击了折叠按钮, 且是展开的
            utils.log("点击了折叠按钮, 且是展开的", arrowSpan);

            const childNodes = Array.from(arrowSpan.parentElement.nextElementSibling.children);
            childNodes.forEach(node => {
                node.querySelector('span.b3-list-item__toggle:not(.fn__hidden)')?.click();
            });
        }

        // 触发处理 点击队列
        async processClickQueue() {
            if (this.isProcessing) return;

            this.isProcessing = true;
            while (this.clickQueue.length) {
                const action = this.clickQueue.shift();
                await action();
            }
            this.isProcessing = false;
        }

        // 处理鼠标点击事件
        handleClickEvent(event) {
            const element = event.target;
            const clickType = this.getClickType(element);
            utils.log("点击了", clickType, element);

            // 处理双击, 系统自带的双击监测事件, 有点问题, 所以自己实现一个
            const currentTime = Date.now();
            if (CONFIG.DOUBLE_SWITCH && currentTime - this.lastClickTime < CONFIG.DOUBLE_INTERVAL &&
                clickType === CLICK_TYPE.NODE) {
                utils.log("双击展开/折叠触发");
                this.doubleClickFlag = true;
                this.clickArrowButton(element);
            }
            this.lastClickTime = currentTime;

            // 处理折叠按钮点击
            if (CONFIG.UNFOLD_ALL_SWITCH && clickType === CLICK_TYPE.ARROW) {
                if (this.doubleClickFlag) {
                    this.doubleClickFlag = false;
                    return;
                }

                utils.log("点击折叠按钮处理", element);
                this.clickQueue.push(async () => {
                    await utils.sleep(CONFIG.UNFOLD_INTERVAL);
                    await this.unfoldChildNodes(element);
                });
                this.processClickQueue();
            }
        }
    }

    // 初始化并监听点击事件
    let initInterval = setInterval(() => {
        const treeContainer = document.querySelector('.sy__file>.fn__flex-1');
        if (treeContainer) {
            clearInterval(initInterval);
            const handler = new TreeHandler();
            treeContainer.addEventListener('click',
                e => handler.handleClickEvent(e), true);
        }
    }, 200);

})();

  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    24687 引用 • 101337 回帖 • 1 关注
  • 代码片段

    代码片段分为 CSS 与 JS 两种代码,添加在 [设置 - 外观 - 代码片段] 中,这些代码会在思源笔记加载时自动执行,用于改善笔记的样式或功能。

    用户在该标签下分享代码片段时需在帖子标题前添加 [css] [js] 用于区分代码片段类型。

    131 引用 • 869 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • EpicJay

    这个好

  • EpicJay

    不过还是改一下,改成双击一键展开,单击折叠按钮还是原来那样打开一级比较好,毕竟一键展开并不是时时会用到,但是文档树展开下一层节点却是高频率事件

EmberSky
如果感觉我的回答对你有帮助, 请点击 感谢 支持一下, 谢谢! 深圳

推荐标签 标签

  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    16 引用 • 236 回帖 • 277 关注
  • 生活

    生活是指人类生存过程中的各项活动的总和,范畴较广,一般指为幸福的意义而存在。生活实际上是对人生的一种诠释。生活包括人类在社会中与自己息息相关的日常活动和心理影射。

    230 引用 • 1454 回帖
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 74 关注
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 547 关注
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 462 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    132 引用 • 1115 回帖 • 122 关注
  • 深度学习

    深度学习(Deep Learning)是机器学习的分支,是一种试图使用包含复杂结构或由多重非线性变换构成的多个处理层对数据进行高层抽象的算法。

    53 引用 • 40 回帖 • 2 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 117 关注
  • Solo

    Solo 是一款小而美的开源博客系统,专为程序员设计。Solo 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    1439 引用 • 10067 回帖 • 490 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖 • 3 关注
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    556 引用 • 674 回帖
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 753 关注
  • Hadoop

    Hadoop 是由 Apache 基金会所开发的一个分布式系统基础架构。用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。

    87 引用 • 122 回帖 • 629 关注
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    950 引用 • 943 回帖
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    20 引用 • 37 回帖 • 569 关注
  • BND

    BND(Baidu Netdisk Downloader)是一款图形界面的百度网盘不限速下载器,支持 Windows、Linux 和 Mac,详细介绍请看这里

    107 引用 • 1281 回帖 • 29 关注
  • 架构

    我们平时所说的“架构”主要是指软件架构,这是有关软件整体结构与组件的抽象描述,用于指导软件系统各个方面的设计。另外还有“业务架构”、“网络架构”、“硬件架构”等细分领域。

    143 引用 • 442 回帖
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    132 引用 • 796 回帖
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 5 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    315 引用 • 547 回帖 • 1 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    79 引用 • 431 回帖 • 1 关注
  • frp

    frp 是一个可用于内网穿透的高性能的反向代理应用,支持 TCP、UDP、 HTTP 和 HTTPS 协议。

    20 引用 • 7 回帖
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 382 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 94 关注