一个很粗浅的插件,第一次点开文件夹时,只显示前三个文件

在使用 ob 的时候,我发现一个小小的痛点:随着时间的推移,有一些文件夹下包含的文件过多,点开后会直接占满整个文件列表,导致在几个文件夹下反复跳跃,寻找相关文件的时候很是费劲,但是其实我所真正需要的,只是最近编辑的几个文件而已,完全不需要打开整个文件夹。

发现没有相关的插件解决这样的情况,我便自己动手写了一个插件,很是简单粗浅,效果如下:

图片

当打开 ob 以后,第一次点开某一个文件夹,不会全部显示出来,而是显示前面的文件和一个小箭头,只有点开这个小箭头以后,才会彻底展开全部的文件,我自己设定为 3 个文件,配合编辑时间从新到旧排序,一下子就清爽不少。

下面是代码,插件我命名为folder-file-limit-plugin:

manifest.json

{
    "id": "folder-file-limit-plugin",
    "name": "Folder File Limit Plugin",
    "version": "1.0.0",
    "minAppVersion": "0.12.0",
    "description": "限制文件夹展开时显示的文件数量,点击展开按钮后才彻底打开。",
    "author": "Olinta",
    "isDesktopOnly": false
}

main.js

const { Plugin } = require("obsidian");

module.exports = class FolderFileLimitPlugin extends Plugin {
    onload() {
        console.log("Folder File Limit Plugin loaded.");

        // 确保文件树加载完成后执行逻辑
        this.app.workspace.onLayoutReady(() => {
            this.initializeFileTreeObserver();
        });
    }

    initializeFileTreeObserver() {

        // 获取文件树容器
        const fileTree = document.querySelector(".workspace");

        if (!fileTree) 
            return;


        // 使用 MutationObserver 监控文件树变化
        const observer = new MutationObserver(() => {
            this.limitFilesInFolders();
        });

        observer.observe(fileTree, {
            childList: true,
            subtree: true, 
        });
    }

    limitFilesInFolders() {

        // 获取所有展开的文件夹
        const folders = document.querySelectorAll(".tree-item.nav-folder:not(.is-collapsed) .nav-folder-children");

        folders.forEach((folder) => {
            // 跳过已完全展开的文件夹
            if (folder.dataset.expanded === "true") return;

            const files = folder.querySelectorAll(".nav-file");
            const existingButton = folder.querySelector(".show-more-button");

            // 如果文件数小于等于 3,移除按钮并标记为已处理
            if (files.length <= 3) {
                if (existingButton) 
                    existingButton.remove();
                folder.dataset.expanded = "true"; 
                return;
            }

            // 显示前 3 个文件,隐藏其余文件
            files.forEach((file, index) => {
                file.style.display = index < 3 ? "" : "none";
            });

            // 如果已有按钮,跳过创建
            if (existingButton) return;

            // 创建“显示更多”按钮
            const showMoreButton = document.createElement("div");
            showMoreButton.classList.add("show-more-button", "down");
            showMoreButton.innerHTML = `
                <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 24 24" width="16" fill="currentColor">
                    <path d="M7 10l5 5 5-5z"/>
                </svg>
            `;
            showMoreButton.style.textAlign = "center";
            showMoreButton.style.margin = "5px auto";
            showMoreButton.style.cursor = "pointer";
            showMoreButton.style.color = "var(--text-normal)";
            showMoreButton.style.display = "flex";
            showMoreButton.style.justifyContent = "center";

            // 按钮点击事件:显示当前文件夹的所有文件
            showMoreButton.addEventListener("click", () => {

                // 显示所有文件
                files.forEach((file) => {
                    file.style.display = "";
                });

                // 标记文件夹为已完全展开
                folder.dataset.expanded = "true";
                showMoreButton.remove();

                // 强制触发 DOM 更新
                folder.dispatchEvent(new Event("DOMSubtreeModified"));
            });

            // 将按钮添加到当前文件夹中
            folder.appendChild(showMoreButton);
        });
    }

    onunload() {
        console.log("Folder File Limit Plugin unloaded.");
    }
};
2 个赞

谢谢楼主,这个功能还蛮好用了

image

能发到github上么, 方便用brat统一进行安装和管理

能帮到你就好啦 :smiley:

收到。现在只是一个最简单的测试版,等周末我有时间再完善一下就传上去

上传好了发一下链接,给你点赞! 需要修改一下,我点到其它文件时,不能自动收起

是说只允许保持一个文件夹完全打开吗?打开一个以后其他就自动收起吗?

我预想的设计是,每一个文件夹的开关状态由文件夹自己单独来管理呢,所以不会影响到其他的文件夹 :open_mouth:

你的想法也行,但是你测试过没有,你重新关闭文件夹在打开,就失效了!必需重新在启动Obsidian!我的Obsidian是1.77版本

是的,现在就是这样的,只有第一次打开会折叠,现在只是一个测试版。

我后面再修改一下,看看是关闭再打开以后就再一次自动折叠,还是在最下面加一个向上折叠按钮。

辛苦了!我个人建议是让它自动折叠,加一个按钮又占空间,又不美观,操作又麻烦!当然你是作者,按你的习惯来!

您好!我又改进了一版,将展开分为了暂时展开和永久展开两个按钮,暂时展开可以在收起文件夹后再一次自动折叠文件夹,同时增加了用户自定义文件数量限制的功能。

不过现在还有一点点小问题:ob 在文件夹中超过大概 30 个文件时,不会一次性将文件全部加载,当用户下滑的时候,才会正式加载。而我的插件是从外部监听文件夹的变化,这就会导致下滑的时候,ob 又加载文件,触发插件渲染,结果就会重复地回到文件列表上方。

我也是刚刚测试的时候才发现这个问题的,这个以现在使用的方案还无法解决,必须从监听外部变为调用 ob 内部的 Api 才行,这个重构代码的工程量有点大,我暂时还没有精力完成,需要等我再研究研究,几个周末可能都搞不出来。

我已经将代码上传 Github,不过由于这个 Bug 的存在,还不能开放下载。我将现在还未完善的版本代码放在下面,如果您需要使用的话可以试试,如果遇到了不可解决的问题,请关闭使用,等待后面的重构,或者看看社区有没有相同思路和想法的插件,感谢。

main.js:

const { Plugin, Setting, PluginSettingTab } = require("obsidian");

module.exports = class FolderFileLimitPlugin extends Plugin {
    DEFAULT_SETTINGS = {
        fileLimit: 5,
    };

    async onload() {
        console.log("Folder File Limit Plugin loaded.");

        this.settings = Object.assign({}, this.DEFAULT_SETTINGS, await this.loadData());

        this.addSettingTab(new FileLimitSettingTab(this.app, this));

        this.app.workspace.onLayoutReady(() => {
            this.initializeFileTreeObserver();
        });
    }

    initializeFileTreeObserver() {
        // 获取文件树容器 
        // Get the file tree container
        const fileTree = document.querySelector(".nav-files-container");

        if (!fileTree) {
            console.warn("File list not found // 未找到文件列表");
            return;
        }

        // 使用 MutationObserver 监控文件树变化
        // Use MutationObserver to monitor changes in the file tree
        const observer = new MutationObserver(() => {
            // 暂时断开观察器 
            // Temporarily disconnect the observer
            observer.disconnect();
            this.limitFilesInFolders();
            // 修改完成后重新启动观察器 
            // Restart the observer after modifications
            observer.observe(fileTree, {
                childList: true,
                subtree: true,
            });
        });

        observer.observe(fileTree, {
            childList: true,
            subtree: true,
        });
    }

    limitFilesInFolders() {
        // 获取所有已经展开的文件夹 
        // Get all expanded folders
        const folders = document.querySelectorAll(".tree-item.nav-folder:not(.is-collapsed) .nav-folder-children");

        folders.forEach((folder) => {
            // 跳过已完全展开的文件夹(带有标记的) 
            // Skip folders that are already fully expanded (marked with a flag)
            if (folder.classList.contains("folders-fully-expanded")) return;

            const files = folder.querySelectorAll(":scope > .nav-file");
            const existingButtonContainer = folder.querySelector(".file-limit-button-container");

            // 如果文件数小于等于 5,移除按钮并标记为已处理 
            // If the number of files is less than or equal to 5, remove the buttons and mark as processed
            if (files.length <= this.settings.fileLimit) {
                if (existingButtonContainer) existingButtonContainer.remove();
                folder.classList.add("folders-fully-expanded");
                return;
            }

            // 显示前 5 个文件,隐藏其余文件 
            // Show the first 5 files and hide the rest
            files.forEach((file, index) => {
                file.style.display = index < this.settings.fileLimit ? "" : "none";
            });

            // 如果已有按钮,跳过创建 
            // Skip creating buttons if they already exist
            if (existingButtonContainer) return;

            // 创建“显示更多”按钮 
            // Create the "Show More" button
            const showMoreButton = new Button({
                className: "show-more-button",
                svg: `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="M12 14.975q-.2 0-.375-.062T11.3 14.7l-4.6-4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.9 3.9l3.9-3.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213t-.375.062"/></svg>
                `,
                onClick: () => {
                    files.forEach((file) => {
                        file.style.display = "";
                    });
                    folder.classList.add("folders-fully-expanded");
                    buttonContainer.remove();
                    // 强制触发 DOM 更新,避免 MutationObserver 误触发
                    folder.dispatchEvent(new Event("DOMSubtreeModified"));

                    setTimeout(() => folder.classList.remove("folders-fully-expanded"), 500);
                },
            }).generate();

            // 创建“永久展开”按钮 
            // Create the "Permanent Expand" button
            const permanentExpandButton = new Button({
                className: "permanent-expand-button",
                svg: `
                <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="currentColor" d="m12 16.175l3.9-3.875q.275-.275.688-.288t.712.288q.275.275.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213t-.375.062t-.375-.062t-.325-.213l-4.6-4.6q-.275-.275-.288-.687T6.7 12.3q.275-.275.7-.275t.7.275zm0-6L15.9 6.3q.275-.275.688-.287t.712.287q.275.275.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213t-.375.062t-.375-.062t-.325-.213L6.7 7.7q-.275-.275-.288-.687T6.7 6.3q.275-.275.7-.275t.7.275z"/></svg>
                `,
                onClick: () => {
                    files.forEach((file) => {
                        file.style.display = "";
                    });
                    // 标记为永久展开 
                    // Mark as permanently expanded
                    folder.classList.add("folders-fully-expanded");
                    buttonContainer.remove();
                },
            }).generate();

            // 创建按钮容器 
            // Create a button container
            const buttonContainer = new ButtonContainer({ showMoreButton, permanentExpandButton }).generate();

            // 将容器添加到文件夹 
            // Append the container to the folder
            folder.appendChild(buttonContainer);
        });
    }

    async saveSettings() {
        await this.saveData(this.settings);
    }

    onunload() {
        console.log("Folder File Limit Plugin unloaded.");
    }
};

class Button {
    constructor({ className, svg, onClick }) {
        this.className = className;
        this.svg = svg;
        this.onClick = onClick;
    }

    generate() {
        const button = document.createElement("div");
        button.classList.add(this.className);
        button.innerHTML = this.svg;
        button.style.display = "inline-flex";
        button.style.margin = "0 7px";
        button.style.cursor = "pointer";
        button.addEventListener("click", this.onClick);
        return button;
    }
}

class ButtonContainer {
    constructor({ showMoreButton, permanentExpandButton }) {
        this.showMoreButton = showMoreButton;
        this.permanentExpandButton = permanentExpandButton;
    }

    generate() {
        const buttonContainer = document.createElement("div");
        buttonContainer.classList.add("file-limit-button-container");
        buttonContainer.style.display = "flex";
        buttonContainer.style.justifyContent = "center";

        buttonContainer.appendChild(this.showMoreButton);
        buttonContainer.appendChild(this.permanentExpandButton);
        return buttonContainer;
    }
}

class FileLimitSettingTab extends PluginSettingTab {
    constructor(app, plugin) {
        super(app, plugin);
        this.plugin = plugin;
    }

    display() {
        const { containerEl } = this;
        containerEl.empty();

        containerEl.createEl("h2", { text: "Folder File Limit Settings" });

        new Setting(containerEl)
            .setName("File Limit")
            .setDesc("Set the maximum number of files to show in expanded folders.")
            .addText((text) =>
                text
                    .setPlaceholder("Enter a number")
                    .setValue(this.plugin.settings.fileLimit.toString())
                    .onChange(async (value) => {
                        if (value.trim() === "") return;

                        const parsedValue = parseInt(value, 10);
                        if (!isNaN(parsedValue) && parsedValue > 0) {
                            this.plugin.settings.fileLimit = parsedValue;
                            await this.plugin.saveSettings();
                        } else {
                            console.warn("Invalid file limit value");
                        }
                    })
            );
    }
}
1 个赞

楼主的这个思路确实不错呀,值得使用。
我有时候也遇到了这个问题,某个文件夹下超过300个文件,想要在这个文件夹添加一个子目录的时候点击一下,需要下滑三四下才能到底,之前是暂时忍耐一下
看到你的这个插件,感觉这个功能还挺有用的,能减少下滑筛选的时间

谢谢!真不错的改进

这个插件有 Github 页面了吗,想先去关注一下 XD

感谢,不过最近没有时间写代码呢,改进之后第一时间就会发一个新贴出来的 :kissing_heart:

1 个赞

非常好的idea,现在的alternative folder tree太慢,就想找这样的。
蹲一个正式版