Olinta
1
在使用 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 个赞
能发到github上么, 方便用brat统一进行安装和管理
Olinta
6
收到。现在只是一个最简单的测试版,等周末我有时间再完善一下就传上去
上传好了发一下链接,给你点赞! 需要修改一下,我点到其它文件时,不能自动收起
Olinta
8
是说只允许保持一个文件夹完全打开吗?打开一个以后其他就自动收起吗?
我预想的设计是,每一个文件夹的开关状态由文件夹自己单独来管理呢,所以不会影响到其他的文件夹
你的想法也行,但是你测试过没有,你重新关闭文件夹在打开,就失效了!必需重新在启动Obsidian!我的Obsidian是1.77版本
Olinta
10
是的,现在就是这样的,只有第一次打开会折叠,现在只是一个测试版。
我后面再修改一下,看看是关闭再打开以后就再一次自动折叠,还是在最下面加一个向上折叠按钮。
辛苦了!我个人建议是让它自动折叠,加一个按钮又占空间,又不美观,操作又麻烦!当然你是作者,按你的习惯来!
Olinta
12
您好!我又改进了一版,将展开分为了暂时展开和永久展开两个按钮,暂时展开可以在收起文件夹后再一次自动折叠文件夹,同时增加了用户自定义文件数量限制的功能。
不过现在还有一点点小问题: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 个赞
qiyue
(qiyue)
13
楼主的这个思路确实不错呀,值得使用。
我有时候也遇到了这个问题,某个文件夹下超过300个文件,想要在这个文件夹添加一个子目录的时候点击一下,需要下滑三四下才能到底,之前是暂时忍耐一下
看到你的这个插件,感觉这个功能还挺有用的,能减少下滑筛选的时间
Moy
15
这个插件有 Github 页面了吗,想先去关注一下 XD
Olinta
16
感谢,不过最近没有时间写代码呢,改进之后第一时间就会发一个新贴出来的
1 个赞
dano
17
非常好的idea,现在的alternative folder tree太慢,就想找这样的。
蹲一个正式版