[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);

})();

  • 思源笔记

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

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

    26046 引用 • 108125 回帖
  • 代码片段

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

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

    189 引用 • 1341 回帖 • 1 关注

相关帖子

欢迎来到这里!

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

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

    这个好

  • EpicJay

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

  • 很实用,感谢

推荐标签 标签

  • 深度学习

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

    43 引用 • 44 回帖
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 406 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 2 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    29 引用 • 230 回帖 • 123 关注
  • FreeMarker

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

    23 引用 • 20 回帖 • 464 关注
  • 宕机

    宕机,多指一些网站、游戏、网络应用等服务器一种区别于正常运行的状态,也叫“Down 机”、“当机”或“死机”。宕机状态不仅仅是指服务器“挂掉了”、“死机了”状态,也包括服务器假死、停用、关闭等一些原因而导致出现的不能够正常运行的状态。

    13 引用 • 82 回帖 • 75 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 3 关注
  • App

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

    91 引用 • 384 回帖
  • 浅吟主题

    Jeffrey Chen 制作的思源笔记主题,项目仓库:https://github.com/TCOTC/Whisper

    1 引用 • 28 回帖 • 3 关注
  • 生活

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

    230 引用 • 1432 回帖
  • GitHub

    GitHub 于 2008 年上线,目前,除了 Git 代码仓库托管及基本的 Web 管理界面以外,还提供了订阅、讨论组、文本渲染、在线文件编辑器、协作图谱(报表)、代码片段分享(Gist)等功能。正因为这些功能所提供的便利,又经过长期的积累,GitHub 的用户活跃度很高,在开源世界里享有深远的声望,并形成了社交化编程文化(Social Coding)。

    209 引用 • 2040 回帖
  • Visio
    1 引用 • 2 回帖
  • Outlook
    1 引用 • 5 回帖 • 5 关注
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    500 引用 • 1395 回帖 • 243 关注
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 157 关注
  • IPFS

    IPFS(InterPlanetary File System,星际文件系统)是永久的、去中心化保存和共享文件的方法,这是一种内容可寻址、版本化、点对点超媒体的分布式协议。请浏览 IPFS 入门笔记了解更多细节。

    20 引用 • 245 回帖 • 233 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖 • 3 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    89 引用 • 1251 回帖 • 395 关注
  • Office

    Office 现已更名为 Microsoft 365. Microsoft 365 将高级 Office 应用(如 Word、Excel 和 PowerPoint)与 1 TB 的 OneDrive 云存储空间、高级安全性等结合在一起,可帮助你在任何设备上完成操作。

    5 引用 • 34 回帖
  • 开源

    Open Source, Open Mind, Open Sight, Open Future!

    413 引用 • 3590 回帖
  • Ruby

    Ruby 是一种开源的面向对象程序设计的服务器端脚本语言,在 20 世纪 90 年代中期由日本的松本行弘(まつもとゆきひろ/Yukihiro Matsumoto)设计并开发。在 Ruby 社区,松本也被称为马茨(Matz)。

    7 引用 • 31 回帖 • 261 关注
  • 服务

    提供一个服务绝不仅仅是简单的把硬件和软件累加在一起,它包括了服务的可靠性、服务的标准化、以及对服务的监控、维护、技术支持等。

    41 引用 • 24 回帖
  • DNSPod

    DNSPod 建立于 2006 年 3 月份,是一款免费智能 DNS 产品。 DNSPod 可以为同时有电信、网通、教育网服务器的网站提供智能的解析,让电信用户访问电信的服务器,网通的用户访问网通的服务器,教育网的用户访问教育网的服务器,达到互联互通的效果。

    6 引用 • 26 回帖 • 537 关注
  • 架构

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

    142 引用 • 442 回帖
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 828 关注
  • SVN

    SVN 是 Subversion 的简称,是一个开放源代码的版本控制系统,相较于 RCS、CVS,它采用了分支管理系统,它的设计目标就是取代 CVS。

    29 引用 • 98 回帖 • 696 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 229 关注