求 js 代码,只展开当前活动页签的文档树

求 js 代码,在使用“始终定位打开的文档”功能时,只展开当前文档的文档树,自动关闭(不展开)其它无关的文档树。例如:

有笔记本 A,内有文档 A1,子文档 A2、子子文档 A3。

有笔记本 B,内有文档 B1,子文档 B2、子子文档 B3。

已将文档 A2、B2 打开,显示在上方页签栏中。

启用“始终定位打开的文档”功能。

点击 A2 页签,左侧文档树自动展开,并定位到 A-A1-A2;A3 列表不展开

点击 B2 页签,左侧文档树自动展开,定位到 B-B1-B2;同时关闭 A-A1-A2 文档树的展开状态,恢复到笔记本 A。也就是仅保留当前活动页签对应的文档树是展开状态,其它文档树分支全部关闭,包括当前活动文档的子文档树。

  • 思源笔记

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

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

    22220 引用 • 88795 回帖 • 2 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    8058 引用 • 36820 回帖 • 161 关注
1 操作
wenbocn 在 2024-06-02 13:54:35 更新了该帖

相关帖子

被采纳的回答
  • wilsons 1

    时隔 1 个多月,你解决了吗?相见即是有缘,看到此贴感觉有些思路,就尝试写了下,实现了这个功能。由于刚接触思源,对思源 api 还不是很了解,所以选择用 js 代码片段实现,纯原生 js 实现,方法比较笨,勉强能用。

    不足之处也请论坛里的大佬们多多批评指正!

    效果如下:

    t2.gif

    完整代码如下:

    // 等待标签页容器渲染完成后开始监听
    whenElementExist('.layout__center').then(async element => {
        // 等待笔记列表加载完毕
        await sleep(40);
        // 监听页签切换事件
        observeTabChanged(element, (tab) => {
            // 折叠所有笔记,然后定位当前笔记
            collapseAllBooksThenFocusCurrentBook(element, tab);
        });
    });
    // 折叠所有笔记,然后定位当前笔记
    async function collapseAllBooksThenFocusCurrentBook(element, tab) {
        // 等待激活文档加载完毕
        await whenElementExist(()=>{
            const content = element.querySelector('.layout-tab-container [data-id="'+tab.getAttribute("data-id")+'"]');
            return content && content.getAttribute("data-loading") === "finished";
        });
        // 折叠所有笔记
        document.querySelectorAll("ul.b3-list[data-url]").forEach(async book => {
            const bookName = tab.getAttribute("aria-label")?.split('/')[0];
            if(bookName) {
                // 如果在本笔记中则不再折叠
                const bookText = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__text')?.innerText;
                if(bookText === bookName) {
                    return;
                }
            }
            // 折叠笔记
            const bookArrowBtn = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__toggle');
            if (bookArrowBtn && bookArrowBtn.firstElementChild.classList.contains("b3-list-item__arrow--open")) {
                bookArrowBtn.click();
            }
        });
        // 定位当前笔记
        document.querySelector(".layout-tab-container .block__icons span[data-type=focus]")?.click();
    }
    // 监听页签切换事件
    function observeTabChanged(parentNode, callback) {
        // 创建一个回调函数来处理观察到的变化
        const observerCallback = function(mutationsList, observer) {
            // 用常规方式遍历 mutationsList 中的每一个 mutation
            for (let mutation of mutationsList) {
                // 属性被修改
                if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                    const element = mutation.target;
                    if (element.tagName.toLowerCase() === 'li' && element.getAttribute('data-type') === 'tab-header' && element.classList.contains('item--focus')) {
                        if(typeof callback === 'function') callback(element);
                    }
                }
                // 如果有新的子节点被添加
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'li') {
                            if (node.getAttribute('data-type') === 'tab-header' && node.classList.contains('item--focus')) {
                                if(typeof callback === 'function') callback(node);
                            }
                        }
                    });
                }
            }
        };
        // 创建一个观察器实例并传入回调函数
        const observer = new MutationObserver(observerCallback);
        // 配置观察器:传递一个对象来指定观察器的行为
        const config = { attributes: true, attributeFilter: ['class'], childList: true, subtree: true };
        // 开始观察目标节点
        observer.observe(parentNode, config);
        // 返回一个函数,用于停止观察
        return function stopObserving() {
            observer.disconnect();
        };
    }
    // 延迟执行
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    // 等待元素渲染完成后执行
    function whenElementExist(selector) {
        return new Promise(resolve => {
            const checkForElement = () => {
                let element = null;
                if (typeof selector === 'function') {
                    element = selector();
                } else {
                    element = document.querySelector(selector);
                }
                if (element) {
                    resolve(element);
                } else {
                    requestAnimationFrame(checkForElement);
                }
            };
            checkForElement();
        });
    }
    

    使用方法:

    设置 》外观 》代码片段 》js 中新增加代码片段,然后把上面的代码粘贴过去即可。

    image.png

欢迎来到这里!

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

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

    时隔 1 个多月,你解决了吗?相见即是有缘,看到此贴感觉有些思路,就尝试写了下,实现了这个功能。由于刚接触思源,对思源 api 还不是很了解,所以选择用 js 代码片段实现,纯原生 js 实现,方法比较笨,勉强能用。

    不足之处也请论坛里的大佬们多多批评指正!

    效果如下:

    t2.gif

    完整代码如下:

    // 等待标签页容器渲染完成后开始监听
    whenElementExist('.layout__center').then(async element => {
        // 等待笔记列表加载完毕
        await sleep(40);
        // 监听页签切换事件
        observeTabChanged(element, (tab) => {
            // 折叠所有笔记,然后定位当前笔记
            collapseAllBooksThenFocusCurrentBook(element, tab);
        });
    });
    // 折叠所有笔记,然后定位当前笔记
    async function collapseAllBooksThenFocusCurrentBook(element, tab) {
        // 等待激活文档加载完毕
        await whenElementExist(()=>{
            const content = element.querySelector('.layout-tab-container [data-id="'+tab.getAttribute("data-id")+'"]');
            return content && content.getAttribute("data-loading") === "finished";
        });
        // 折叠所有笔记
        document.querySelectorAll("ul.b3-list[data-url]").forEach(async book => {
            const bookName = tab.getAttribute("aria-label")?.split('/')[0];
            if(bookName) {
                // 如果在本笔记中则不再折叠
                const bookText = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__text')?.innerText;
                if(bookText === bookName) {
                    return;
                }
            }
            // 折叠笔记
            const bookArrowBtn = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__toggle');
            if (bookArrowBtn && bookArrowBtn.firstElementChild.classList.contains("b3-list-item__arrow--open")) {
                bookArrowBtn.click();
            }
        });
        // 定位当前笔记
        document.querySelector(".layout-tab-container .block__icons span[data-type=focus]")?.click();
    }
    // 监听页签切换事件
    function observeTabChanged(parentNode, callback) {
        // 创建一个回调函数来处理观察到的变化
        const observerCallback = function(mutationsList, observer) {
            // 用常规方式遍历 mutationsList 中的每一个 mutation
            for (let mutation of mutationsList) {
                // 属性被修改
                if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
                    const element = mutation.target;
                    if (element.tagName.toLowerCase() === 'li' && element.getAttribute('data-type') === 'tab-header' && element.classList.contains('item--focus')) {
                        if(typeof callback === 'function') callback(element);
                    }
                }
                // 如果有新的子节点被添加
                if (mutation.type === 'childList') {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'li') {
                            if (node.getAttribute('data-type') === 'tab-header' && node.classList.contains('item--focus')) {
                                if(typeof callback === 'function') callback(node);
                            }
                        }
                    });
                }
            }
        };
        // 创建一个观察器实例并传入回调函数
        const observer = new MutationObserver(observerCallback);
        // 配置观察器:传递一个对象来指定观察器的行为
        const config = { attributes: true, attributeFilter: ['class'], childList: true, subtree: true };
        // 开始观察目标节点
        observer.observe(parentNode, config);
        // 返回一个函数,用于停止观察
        return function stopObserving() {
            observer.disconnect();
        };
    }
    // 延迟执行
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    // 等待元素渲染完成后执行
    function whenElementExist(selector) {
        return new Promise(resolve => {
            const checkForElement = () => {
                let element = null;
                if (typeof selector === 'function') {
                    element = selector();
                } else {
                    element = document.querySelector(selector);
                }
                if (element) {
                    resolve(element);
                } else {
                    requestAnimationFrame(checkForElement);
                }
            };
            checkForElement();
        });
    }
    

    使用方法:

    设置 》外观 》代码片段 》js 中新增加代码片段,然后把上面的代码粘贴过去即可。

    image.png

    4 回复
  • 其他回帖
  • 发了 https://sypai.cc/archives/977/

    @wenbocn 从代码中提取的这段代码,本意是想通过标签的 aria-label 属性实现,如果展开的笔记是当前点击的文档所在笔记不再折叠再展开了,这样防止出现跳动。但现在想来如果同一个笔记下,展开多个目录可能会导致同个笔记下的文档切换时不再折叠,不知道是否符合你的需求,如果不符合,建议把这段代码删除。而且这段代码在打开文档时还会出现跳动,实现并不完美。

    const bookName = tab.getAttribute("aria-label")?.split('/')[0];
            if(bookName) {
                // 如果在本笔记中则不再折叠
                const bookText = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__text')?.innerText;
                if(bookText === bookName) {
                    return;
                }
            }
    

    比如,如下场景不会折叠同个笔记下的目录

    t4.gif

    2 回复
  • wenbocn

    使用 ChatGPT 修改了代码,点击文档树,不会有跳动现象了。但点击标签页及正文后的输入焦点在文档内,能够正常编辑文档,但显示焦点却在文档树的按钮上,不知咋回事。

    // 等待元素渲染完成后执行
    function whenElementExist(selector) {
        return new Promise(resolve => {
            const checkForElement = () => {
                const element = typeof selector === 'function' ? selector() : document.querySelector(selector);
                if (element) {
                    resolve(element);
                } else {
                    requestAnimationFrame(checkForElement);
                }
            };
            checkForElement();
        });
    }
    
    // 延迟执行
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    // 折叠所有笔记,然后定位当前笔记
    async function collapseAllBooksThenFocusCurrentBook(tab) {
        const currentBookName = tab.getAttribute("aria-label")?.split('/')[0];
    
        // 等待文档加载完毕
        await whenElementExist(() => {
            const content = document.querySelector('.layout-tab-container [data-id="' + tab.getAttribute("data-id") + '"]');
            return content && content.getAttribute("data-loading") === "finished";
        });
    
        // 折叠所有笔记
        document.querySelectorAll("ul.b3-list[data-url]").forEach(book => {
            const bookName = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__text')?.innerText;
            if (bookName !== currentBookName) {
                const bookArrowBtn = book.querySelector('li[data-type="navigation-root"] span.b3-list-item__toggle');
                if (bookArrowBtn && bookArrowBtn.firstElementChild.classList.contains("b3-list-item__arrow--open")) {
                    bookArrowBtn.click();
                }
            }
        });
    
        // 定位当前笔记
        document.querySelector(".layout-tab-container .block__icons span[data-type=focus]")?.click();
    }
    
    // 监听页签切换事件
    function observeTabChanged(callback) {
        const observerCallback = (mutationsList) => {
            mutationsList.forEach(mutation => {
                if ((mutation.type === 'attributes' && mutation.attributeName === 'class') ||
                    (mutation.type === 'childList' && mutation.addedNodes.length > 0)) {
                    const focusedTab = mutation.target.closest('li[data-type="tab-header"].item--focus');
                    if (focusedTab) callback(focusedTab);
                }
            });
        };
    
        const observer = new MutationObserver(observerCallback);
        observer.observe(document.querySelector('.layout__center'), { attributes: true, childList: true, subtree: true });
        return () => observer.disconnect();
    }
    
    // 主函数,等待容器渲染后开始监听
    whenElementExist('.layout__center').then(async () => {
        await sleep(40);
        observeTabChanged(collapseAllBooksThenFocusCurrentBook);
    });
    
    
    1 回复
  • wilsons 2

    @wenbocn 我已完美实现了不闪动的版本,并且改进了官方定位,在未打开目录树时,左侧 dock 区目录树显示隐藏按钮样式会获取焦点的 bug

    效果如下

    t6.gif

    完整代码如下

    由于链滴代码长度有限制,只能放到 jsrun 了。

    3 回复
  • 查看全部回帖

推荐标签 标签

  • FreeMarker

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

    23 引用 • 20 回帖 • 460 关注
  • RYMCU

    RYMCU 致力于打造一个即严谨又活泼、专业又不失有趣,为数百万人服务的开源嵌入式知识学习交流平台。

    4 引用 • 6 回帖 • 52 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 23 关注
  • 数据库

    据说 99% 的性能瓶颈都在数据库。

    339 引用 • 708 回帖
  • 链滴

    链滴是一个记录生活的地方。

    记录生活,连接点滴

    153 引用 • 3783 回帖 • 2 关注
  • PostgreSQL

    PostgreSQL 是一款功能强大的企业级数据库系统,在 BSD 开源许可证下发布。

    22 引用 • 22 回帖 • 1 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    197 引用 • 548 回帖
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 1 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 638 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    15 引用 • 7 回帖
  • WebComponents

    Web Components 是 W3C 定义的标准,它给了前端开发者扩展浏览器标签的能力,可以方便地定制可复用组件,更好的进行模块化开发,解放了前端开发者的生产力。

    1 引用 • 3 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 628 关注
  • JetBrains

    JetBrains 是一家捷克的软件开发公司,该公司位于捷克的布拉格,并在俄国的圣彼得堡及美国麻州波士顿都设有办公室,该公司最为人所熟知的产品是 Java 编程语言开发撰写时所用的集成开发环境:IntelliJ IDEA

    18 引用 • 54 回帖 • 2 关注
  • 996
    13 引用 • 200 回帖 • 6 关注
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 1 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 4 关注
  • Redis

    Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware 主持。从 2013 年 5 月开始,Redis 的开发由 Pivotal 赞助。

    286 引用 • 248 回帖 • 67 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖
  • 强迫症

    强迫症(OCD)属于焦虑障碍的一种类型,是一组以强迫思维和强迫行为为主要临床表现的神经精神疾病,其特点为有意识的强迫和反强迫并存,一些毫无意义、甚至违背自己意愿的想法或冲动反反复复侵入患者的日常生活。

    15 引用 • 161 回帖 • 1 关注
  • NGINX

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

    311 引用 • 546 回帖
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 657 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    90 引用 • 899 回帖
  • 服务

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

    41 引用 • 24 回帖 • 3 关注
  • GitBook

    GitBook 使您的团队可以轻松编写和维护高质量的文档。 分享知识,提高团队的工作效率,让用户满意。

    3 引用 • 8 回帖
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 6 关注
  • 知乎

    知乎是网络问答社区,连接各行各业的用户。用户分享着彼此的知识、经验和见解,为中文互联网源源不断地提供多种多样的信息。

    10 引用 • 66 回帖