[js] 各种小界面的过滤器,更多功能等接力

好家伙!原来没留意——发帖还要扣分的,亏本分享

虽然对我来说,分有啥用啊~所以我更新了……

看到隔壁讨论的需求,标签搜索功能 - 链滴

本来想直接回复,字数超了

image.png

论坛有过分享的

[js] 大纲过滤器, 支持大部分面板的过滤 (大纲, 标签, 书签, 书签 +, 文件树, 反链) - 链滴

站在大佬肩膀上按个人需求完善了下,按需修改吧,我才学的 js

// add filters for all各种过滤器
// 原作者版本https://ld246.com/article/1736828257787 https://github.com/leeyaunlong/siyuan_scripts/
// chuchen接力版:增加按钮循环控制,css优化,支持拼音模糊搜索,
// 支持混合逻辑语法:'空格 '分割关键词(AND逻辑),'竖线|'分割关键词(OR逻辑),'英文感叹号!'开头的关键词(NOT逻辑),暂不支持转义
// 待完善功能,支持闪卡过滤器,支持手机版
(() => {
    // 定义过滤器配置
    const filterConfigs = [
        { id: 'tree_filter_container', placeholder: "文档树过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__file', positionMark: 1 },
        { id: 'outline_filter_container', placeholder: "大纲过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__outline', positionMark: 1 },
        { id: 'bmsp_filter_container', placeholder: "书签+过滤器", selector: '.fn__flex-1.b3-list.b3-list--background.custom-bookmark-body', positionMark: 3 },
        { id: 'tags_filter_container', placeholder: "标签过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__tag', positionMark: 2 },
        { id: 'bms_filter_container', placeholder: "书签过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__bookmark', positionMark: 2 },
        { id: 'backlink_filter_container', placeholder: "反链过滤器", selector: '.fn__flex-1.fn__flex-column.file-tree.sy__backlink', positionMark: 2 },
        // { id: 'flashcard_filter_container', placeholder: "闪卡过滤器", selector: '[data - key= "dialog-viewcards"] .fn__flex - column', positionMark: 2 }
    ];
    // 检查是否为手机版
    function isMobile() {
        return !!document.getElementById("sidebar");
    }
    // 等待元素出现
    function whenElementExist(selector, node = document, timeout = 5000) {
        return new Promise((resolve, reject) => {
            const start = Date.now();
            function check() {
                let el;
                try {
                    el = typeof selector === 'function'
                        ? selector()
                        : node.querySelector(selector);
                } catch (err) {
                    return reject(err);
                }
                if (el) {
                    resolve(el);
                } else if (Date.now() - start >= timeout) {
                    reject(new Error(`Timed out after ${timeout}ms waiting for element ${selector}`));
                } else {
                    requestAnimationFrame(check);
                }
            }
            check();
        });
    }
    // 支持拼音模糊过滤
    const loadPinyin = new Promise((resolve) => {
        // 优先检查现有库
        if (window.pinyinPro) return resolve(window.pinyinPro);
        // 定义多个CDN源
        const sources = [
            'https://cdn.bootcdn.net/ajax/libs/pinyin-pro/3.26.0/index.min.js',
            'https://unpkg.com/pinyin-pro@3.18.2/dist/index.js',
            'https://cdn.jsdelivr.net/npm/pinyin-pro@3.18.2/dist/index.js',
            '/plugins/myFonts/pinyin-pro.min.js'
        ];
        let retryCount = 0;
        const tryNextSource = () => {
            if (retryCount >= sources.length) {
                console.warn('代码片段-过滤器-所有拼音源加载失败,启用纯文本过滤');
                return resolve(null);
            }
            const script = document.createElement('script');
            script.src = sources[retryCount];
            script.onload = () => resolve(window.pinyinPro);
            script.onerror = () => {
                retryCount++;
                tryNextSource();
            };
            document.head.appendChild(script);
        };
        tryNextSource();
    });
    // 在addFilterMo函数中修改input事件监听器:
    function addFilterMo({ id, placeholder, selector, positionMark }) {
        const existingInput = document.getElementById(id);
        if (existingInput) existingInput.remove();
        const container = document.createElement('div');
        container.id = id;
        container.style.display = 'flex';
        container.style.alignItems = 'center';
        const input = document.createElement('input');
        input.id = 'outline_filter';
        input.type = 'text';
        input.placeholder = placeholder;
        input.style.flex = '1';
        input.style.minWidth = 0;
        input.style.borderWidth = '1px';
        input.style.borderStyle = 'dashed';
        input.style.borderColor = 'var(--b3-theme-on-surface)';
        input.style.backgroundColor = 'var(--b3-theme-surface)';
        input.style.color = 'var(--b3-theme-on-background)';
        input.className = 'b3-text-field fn__block';
        const resetButton = document.createElement('button');
        resetButton.textContent = '↺';
        resetButton.style.color = 'var(--color-text-3)';
        resetButton.style.fontWeight = "900";
        resetButton.style.cursor = 'pointer';
        resetButton.style.backgroundColor = 'var(--b3-theme-surface-light)';
        resetButton.style.borderWidth = '1px';
        resetButton.style.borderStyle = 'dotted';
        resetButton.style.borderColor = 'var(--b3-theme-on-surface)';
        resetButton.style.marginRight = '3px';
        const closeButton = document.createElement('button');
        closeButton.textContent = '⨉';
        closeButton.style.color = 'var(--color-text-3)';
        closeButton.style.fontWeight = "900";
        closeButton.style.cursor = 'pointer';
        closeButton.style.backgroundColor = 'var(--b3-theme-surface-light)';
        closeButton.style.borderWidth = '1px';
        closeButton.style.borderStyle = 'dotted';
        closeButton.style.borderColor = 'var(--b3-theme-on-surface)';
        closeButton.style.marginLeft = '3px';
        container.appendChild(resetButton);
        container.appendChild(input);
        container.appendChild(closeButton);
        const targetElement = document.querySelector(selector);
        if (targetElement) {
            if (positionMark === 0) {
                targetElement.parentElement.insertBefore(container, targetElement);
            } else if (positionMark === 1) {
                targetElement.parentElement.insertAdjacentElement('beforebegin', container);
            } else if (positionMark === 2) {
                targetElement.appendChild(container);
            } else if (positionMark === 3) {
                targetElement.parentElement.insertAdjacentElement('beforeend', container);
            } else if (positionMark === 4) {
                targetElement.prepend(container);
            } else if (positionMark === 5) {
                targetElement.parentElement.insertAdjacentElement('afterebegin', container);
            }
        }
        const targetElement1 = document.querySelector(selector + '.fn__none');
        if (targetElement1) {
            container.remove(); // 移除输入框容器
        }
        const resetDisplay = () => {
            const spans = document.querySelectorAll(selector + ' li span.b3-list-item__text.ariaLabel');
            spans.forEach(span => {
                const listItem = span.parentElement;
                listItem.style.display = ''; // 重置所有项的显示状态
            });
        };
        // input.focus(); // 添加自动聚焦
        // 修改输入事件处理中的拼音调用部分
        input.addEventListener('input', async function () {
            const filterText = input.value.toLowerCase();
            // 分割逻辑组(OR)和条件(AND/NOT)
            const orGroups = filterText.split('|').map(group =>
                group.split(' ').filter(k => k.trim()).map(term => ({
                    value: term.replace(/^!/, ''),
                    isNot: term.startsWith('!')
                }))
            );

            const spans = document.querySelectorAll(selector + ' li span.b3-list-item__text.ariaLabel');
            try {
                const pinyin = await loadPinyin;
                if (!pinyin?.pinyin) throw new Error('代码片段-过滤器拼音库初始化失败');
                spans.forEach(span => {
                    const listItem = span.parentElement;
                    const text = span.textContent;

                    // 生成拼音数据
                    const pinyinInitials = pinyin.pinyin(text, {
                        pattern: 'first',
                        type: 'array',
                        toneType: 'none',
                        multiple: true
                    }).join('').toLowerCase();

                    const pinyinFull = pinyin.pinyin(text, {
                        pattern: 'pinyin',
                        type: 'array',
                        toneType: 'none',
                        multiple: true
                    }).join('').toLowerCase();

                    // OR逻辑:任意一组满足即显示
                    const matchAnyGroup = orGroups.some(group => {
                        // AND逻辑:组内所有条件必须满足
                        return group.every(({ value, isNot }) => {
                            const hasMatch = [
                                text.toLowerCase().includes(value),
                                pinyinInitials.includes(value),
                                pinyinFull.includes(value)
                            ].some(Boolean);

                            return isNot ? !hasMatch : hasMatch;
                        });
                    });

                    listItem.style.display = matchAnyGroup ? '' : 'none';
                });
            } catch (e) {
                // 降级处理
                spans.forEach(span => {
                    const text = span.textContent.toLowerCase();
                    const matchAnyGroup = orGroups.some(group =>
                        group.every(({ value, isNot }) => {
                            const hasMatch = text.includes(value);
                            return isNot ? !hasMatch : hasMatch;
                        })
                    );
                    span.parentElement.style.display = matchAnyGroup ? '' : 'none';
                });
            }
        });
        resetButton.addEventListener('click', function () {
            input.value = ''; // 清空输入框
            resetDisplay(); // 重置显示状态
        });
        closeButton.addEventListener('click', function () {
            input.value = ''; // 清空输入框
            resetDisplay(); // 重置显示状态
            container.remove(); // 移除输入框容器
        });
    }
    // 设置所有过滤器
    function set_addFilter() {
        filterConfigs.forEach(config => {
            addFilterMo(config);
        });
    }
    // 检查并移除所有过滤器
    function checkFilter() {
        filterConfigs.forEach(config => {
            const fCs = document.getElementById(config.id);
            if (fCs) {
                fCs.remove();
            }
        });
    }
    // 添加按钮
    function addButton(vtext, pin) {
        let flag = false;
        const button = document.createElement('span');
        button.className = 'dock__item ariaLabel';
        button.textContent = vtext;
        button.setAttribute('aria-label', '      关闭筛选');
        button.onclick = (event) => {
            event.preventDefault();
            event.stopPropagation();
            checkFilter();
            if (flag) {
                set_addFilter();
                startTabObserver('.layout-tab-container.fn__flex-1', 'layout__tab--active', tabObservers, set_addFilter); // 启用筛选时启动监听
                observer2.observe(targetBody, config2);
            } else {
                stopTabObserver(tabObservers);  // 禁用筛选时停止监听
                observer2.disconnect()
            }
            flag = !flag;
            button.setAttribute('aria-label', flag ? '      开启筛选' : '      关闭筛选');
            button.textContent = flag ? '🕸︎' : '🕸️';
        };
        pin.before(button);
    }
    // Tab切换监听器
    let tabObservers = [];
    // 启动Tab切换监听
    function startTabObserver(fatherClass, childClass, obArray, callback) {
        if (obArray.length > 0) return;
        whenAllElementsExist(fatherClass).then((parentElements) => {
            parentElements.forEach((parentElement) => {
                const observer = new MutationObserver((mutationsList) => {
                    for (const mutation of mutationsList) {
                        if (mutation.type === 'childList') {
                            mutation.addedNodes.forEach(node => {
                                if (node.classList?.contains(childClass)) {
                                    callback();
                                }
                            });
                        }
                        if (mutation.type === 'attributes' && mutation.target.classList?.contains(childClass)) {
                            callback();
                        }
                    }
                });
                observer.observe(parentElement, {
                    childList: true,
                    subtree: true,
                    attributes: true,
                    attributeFilter: ['class']
                });
                obArray.push(observer);
            });
        }).catch(() => {
            // 后备方案:监听整个文档
            const documentObserver = new MutationObserver((mutationsList) => {
                for (const mutation of mutationsList) {
                    if (mutation.target.classList?.contains(childClass)) {
                        callback();
                    }
                }
            });
            documentObserver.observe(document, {
                childList: true,
                subtree: true,
                attributes: true,
                attributeFilter: ['class']
            });
            obArray.push(documentObserver);
        });
    };
    // 停止Tab切换监听
    function stopTabObserver(obArray) {
        obArray.forEach(observer => observer.disconnect());
        obArray.length = 0;
    }
    // 等待所有匹配元素出现
    function whenAllElementsExist(selector) {
        return new Promise(resolve => {
            const check = () => {
                const elements = document.querySelectorAll(selector);
                if (elements.length > 0) resolve(Array.from(elements));
                else requestAnimationFrame(check);
            };
            check();
        });
    }
    // 手机版返回
    if (isMobile()) return;
    // 监听dock加载完毕
    whenElementExist('#dockLeft .dock__items .dock__item--pin').then((pin) => {
        let vtext = "🕸️";
        addButton(vtext, pin);
        set_addFilter();
        startTabObserver('.layout-tab-container.fn__flex-1', 'layout__tab--active', tabObservers, set_addFilter);
    });
    // whenAllElementsExist('.b3-dialog--open').then(() => {
    //     set_addFilter();
    // });
    // 获取要观察的目标节点
    const targetBody = document.querySelector('body');
    // 配置观察选项
    const config2 = { childList: true, subtree: true };
    // 定义一个回调函数,当观察到DOM变化时会被调用
    const callback2 = function (mutationsList, observer2) {
        for (let mutation of mutationsList) {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE && node.matches('[data-key="dialog-viewcards"].b3-dialog--open')) {
                        // console.log('5555The specific div has been added!');
                        set_addFilter();
                    }
                });
            }
        }
    };
    // 创建一个观察器实例并传入回调函数
    const observer2 = new MutationObserver(callback2);
})();

  • 思源笔记

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

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

    26351 引用 • 109581 回帖
  • 代码片段

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

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

    203 引用 • 1473 回帖
4 操作
chuchen 在 2025-07-06 14:59:11 更新了该帖
chuchen 在 2025-07-05 17:00:10 更新了该帖
chuchen 在 2025-07-05 16:36:39 更新了该帖
chuchen 在 2025-07-05 15:46:28 更新了该帖

相关帖子

欢迎来到这里!

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

注册 关于
请输入回帖内容 ...
  • ZQ11 1 评论

    做的非常好 感觉可以做成一个插件 做得更好一点哈哈哈

    其实我现在也没理解什么场景一定需要用到插件,那比如这个功能,可能用到插件就是可以记录 关闭 这个过滤功能之后 下 打开 思源笔记的时候就能按你选择的来。但是代码片段就很灵活呀 大家都可见的,随时可以更新
    chuchen
  • nightstars 4 评论

    请问你是怎么学的,报班吗?我也想学一下 js 代码,

    刷 b 站的视频,找你能听进去那些,我都开的 2 倍速,先对 js 有个基本概念,知道 dom 是怎么回事,然后在论坛看了各位大佬的代码片段,知道思源的前端是怎么跑起来,然后就结合 ai 了……
    chuchen
    @chuchen 请问,听的时候,还用做笔记吗?还是说,只听就行?
    nightstars
    看你学习习惯 对我来说做笔记还是有必要吧 但更关键的是学计算机语言还是需要动手的,按刻意练习的方法来 实操过才深刻,才能印证一些想法,刚开始的时候有很多似是而非的,当然也不用着急,先一步步建立心理表征,后面在实例里就知道是怎么用的了,我基础也不算扎实,
    chuchen
    忘了说 其实我看的那位老师直接有分享笔记的,评论区也有人分享,我是在那个基础上整理,毕竟获取得太轻松了,没太入脑。另外,现在学习语言变得轻松的很重要的一点是——啥不懂直接问 AI,就很细的知识点,也不需要多智能的 AI,免费的就行,基本都能带出很多相关的知识点。其实这事你只要开始就很快有收获的
    chuchen

推荐标签 标签

  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 565 关注
  • Access
    1 引用 • 3 回帖 • 3 关注
  • Vim

    Vim 是类 UNIX 系统文本编辑器 Vi 的加强版本,加入了更多特性来帮助编辑源代码。Vim 的部分增强功能包括文件比较(vimdiff)、语法高亮、全面的帮助系统、本地脚本(Vimscript)和便于选择的可视化模式。

    29 引用 • 66 回帖
  • Sym

    Sym 是一款用 Java 实现的现代化社区(论坛/BBS/社交网络/博客)系统平台。

    下一代的社区系统,为未来而构建

    524 引用 • 4601 回帖 • 710 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    7 引用 • 69 回帖 • 5 关注
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 799 关注
  • DNSPod

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

    6 引用 • 26 回帖 • 533 关注
  • 程序员

    程序员是从事程序开发、程序维护的专业人员。

    591 引用 • 3528 回帖
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 70 关注
  • 服务器

    服务器,也称伺服器,是提供计算服务的设备。由于服务器需要响应服务请求,并进行处理,因此一般来说服务器应具备承担服务并且保障服务的能力。

    125 引用 • 585 回帖
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 734 关注
  • Eclipse

    Eclipse 是一个开放源代码的、基于 Java 的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。

    76 引用 • 258 回帖 • 624 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 642 关注
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖
  • 招聘

    哪里都缺人,哪里都不缺人。

    188 引用 • 1057 回帖 • 1 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖 • 4 关注
  • PHP

    PHP(Hypertext Preprocessor)是一种开源脚本语言。语法吸收了 C 语言、 Java 和 Perl 的特点,主要适用于 Web 开发领域,据说是世界上最好的编程语言。

    167 引用 • 408 回帖 • 485 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 15 关注
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 522 关注
  • RYMCU

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

    4 引用 • 6 回帖 • 62 关注
  • 笔记

    好记性不如烂笔头。

    311 引用 • 794 回帖
  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    173 引用 • 414 回帖 • 364 关注
  • ngrok

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

    7 引用 • 63 回帖 • 657 关注
  • Firefox

    Mozilla Firefox 中文俗称“火狐”(正式缩写为 Fx 或 fx,非正式缩写为 FF),是一个开源的网页浏览器,使用 Gecko 排版引擎,支持多种操作系统,如 Windows、OSX 及 Linux 等。

    7 引用 • 30 回帖 • 376 关注
  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 184 关注
  • 设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

    201 引用 • 120 回帖 • 2 关注
  • Redis

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

    284 引用 • 248 回帖