mirror of
https://github.com/ozntel/file-explorer-note-count.git
synced 2026-07-22 05:40:24 +00:00
perf: use debounce for vault event
- now the returned path of all vault event is kept in waitingList and only get update when all consecutive events are finished - add filters to reduce duplicate count update
This commit is contained in:
parent
a6a73950bb
commit
a4498fdec9
5 changed files with 99 additions and 62 deletions
|
|
@ -23,6 +23,7 @@
|
|||
"@types/node": "^14.14.37",
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
"assert-never": "^1.2.1",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^7.26.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
|
|
|
|||
|
|
@ -5,10 +5,11 @@ import {
|
|||
AbstractFileFilter,
|
||||
getParentPath,
|
||||
isFolder,
|
||||
isParent,
|
||||
iterateItems,
|
||||
withSubfolderClass,
|
||||
} from 'misc';
|
||||
import { AFItem, FolderItem, TAbstractFile, TFolder } from 'obsidian';
|
||||
import { AFItem, FolderItem, TFolder } from 'obsidian';
|
||||
|
||||
const countFolderChildren = (folder: TFolder, filter: AbstractFileFilter) => {
|
||||
let count = 0;
|
||||
|
|
@ -19,38 +20,53 @@ const countFolderChildren = (folder: TFolder, filter: AbstractFileFilter) => {
|
|||
return count;
|
||||
};
|
||||
|
||||
/** filter out all path that is the parent of existing path */
|
||||
const filterParent = (pathList: string[]): Set<string> => {
|
||||
const list = Array.from(pathList);
|
||||
list.sort();
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (
|
||||
i < list.length - 1 &&
|
||||
(list[i] === list[i + 1] || isParent(list[i], list[i + 1]))
|
||||
) {
|
||||
list.shift();
|
||||
i--;
|
||||
}
|
||||
}
|
||||
return new Set(list);
|
||||
};
|
||||
/** get all parents and add to set if not exist */
|
||||
const getAllParents = (path: string, set: Set<string>): void => {
|
||||
let parent = getParentPath(path);
|
||||
while (parent && !set.has(parent)) {
|
||||
set.add(parent);
|
||||
parent = getParentPath(parent);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Update folder count of target's parent
|
||||
*/
|
||||
export const updateCount = (
|
||||
target: string | TAbstractFile,
|
||||
targetList: string[],
|
||||
plugin: FileExplorerNoteCount,
|
||||
): void => {
|
||||
if (!plugin.fileExplorer) throw new Error('fileExplorer not found');
|
||||
const explorer = plugin.fileExplorer;
|
||||
|
||||
const iterate = (folder: TFolder) => {
|
||||
if (!folder.isRoot()) {
|
||||
setCount(
|
||||
explorer.fileItems[folder.path] as FolderItem,
|
||||
plugin.fileFilter,
|
||||
);
|
||||
iterate(folder.parent);
|
||||
}
|
||||
};
|
||||
|
||||
let parent: TFolder;
|
||||
if (typeof target === 'string' || !target.parent) {
|
||||
const filePath = typeof target === 'string' ? target : target.path;
|
||||
const parentPath = getParentPath(filePath);
|
||||
parent = plugin.app.vault.getAbstractFileByPath(parentPath) as TFolder;
|
||||
if (!parent) {
|
||||
console.error('cannot find parent: ' + parentPath);
|
||||
return;
|
||||
}
|
||||
} else parent = target.parent;
|
||||
|
||||
iterate(parent);
|
||||
const set = filterParent(targetList);
|
||||
for (const path of targetList) {
|
||||
getAllParents(path, set);
|
||||
}
|
||||
// set count of path
|
||||
const { fileExplorer, fileFilter } = plugin;
|
||||
if (!fileExplorer) {
|
||||
console.error('fileExplorer missing');
|
||||
return;
|
||||
}
|
||||
for (const path of set) {
|
||||
// check if path available
|
||||
if (!fileExplorer.fileItems[path]) continue;
|
||||
setCount(fileExplorer.fileItems[path] as FolderItem, fileFilter);
|
||||
}
|
||||
// empty waitingList
|
||||
targetList.length = 0;
|
||||
};
|
||||
|
||||
export const setupCount = (plugin: FileExplorerNoteCount, revert = false) => {
|
||||
|
|
@ -63,7 +79,7 @@ export const setupCount = (plugin: FileExplorerNoteCount, revert = false) => {
|
|||
});
|
||||
};
|
||||
|
||||
const setCount = (item: FolderItem, filter: AbstractFileFilter) => {
|
||||
export const setCount = (item: FolderItem, filter: AbstractFileFilter) => {
|
||||
if (item.file.isRoot()) return;
|
||||
const count = countFolderChildren(item.file, filter);
|
||||
item.titleEl.dataset['count'] = count.toString();
|
||||
|
|
|
|||
39
src/main.ts
39
src/main.ts
|
|
@ -1,10 +1,10 @@
|
|||
import './styles/patch.css';
|
||||
|
||||
import { AbstractFileFilter, getParentPath, withSubfolderClass } from 'misc';
|
||||
import { FileExplorer, Plugin, TAbstractFile, TFile, TFolder } from 'obsidian';
|
||||
import { dirname } from 'path-browserify';
|
||||
import { AbstractFileFilter, withSubfolderClass } from 'misc';
|
||||
import { FileExplorer, Plugin, TFile } from 'obsidian';
|
||||
import { VaultHandler } from 'vault-handler';
|
||||
|
||||
import { setupCount, updateCount } from './folder-count';
|
||||
import { setupCount } from './folder-count';
|
||||
import { DEFAULT_SETTINGS, FENoteCountSettingTab } from './settings';
|
||||
|
||||
export default class FileExplorerNoteCount extends Plugin {
|
||||
|
|
@ -12,34 +12,7 @@ export default class FileExplorerNoteCount extends Plugin {
|
|||
|
||||
fileExplorer?: FileExplorer;
|
||||
|
||||
onRename = (af: TAbstractFile, oldPath: string) => {
|
||||
// only update when file is moved to other location
|
||||
// if af is TFolder, its count will be updated when its children are renamed
|
||||
if (af instanceof TFolder && dirname(af.path) === dirname(oldPath))
|
||||
return;
|
||||
|
||||
updateCount(af, this);
|
||||
const oldParent = getParentPath(oldPath);
|
||||
// when file is moved alone (not with folder)
|
||||
if (this.app.vault.getAbstractFileByPath(oldParent))
|
||||
updateCount(oldPath, this);
|
||||
};
|
||||
|
||||
registerVaultEvent() {
|
||||
// attach events on new folder
|
||||
this.registerEvent(
|
||||
this.app.vault.on('create', (af) => {
|
||||
updateCount(af, this);
|
||||
}),
|
||||
);
|
||||
// include mv and rename
|
||||
this.registerEvent(this.app.vault.on('rename', this.onRename));
|
||||
this.registerEvent(
|
||||
this.app.vault.on('delete', (af) => {
|
||||
updateCount(af, this);
|
||||
}),
|
||||
);
|
||||
}
|
||||
vaultHandler = new VaultHandler(this);
|
||||
|
||||
initialize = (revert = false) => {
|
||||
const leaves = this.app.workspace.getLeavesOfType('file-explorer');
|
||||
|
|
@ -49,7 +22,7 @@ export default class FileExplorerNoteCount extends Plugin {
|
|||
if (!this.fileExplorer)
|
||||
this.fileExplorer = leaves[0].view as FileExplorer;
|
||||
setupCount(this, revert);
|
||||
if (!revert) this.registerVaultEvent();
|
||||
if (!revert) this.vaultHandler.registerVaultEvent();
|
||||
if (revert) {
|
||||
for (const el of document.getElementsByClassName(
|
||||
withSubfolderClass,
|
||||
|
|
|
|||
11
src/misc.ts
11
src/misc.ts
|
|
@ -22,7 +22,8 @@ export const iterateItems = (
|
|||
}
|
||||
};
|
||||
|
||||
export const getParentPath = (src: string) => {
|
||||
export const getParentPath = (src: string): string | null => {
|
||||
if (src === '/') return null;
|
||||
const path = dirname(src);
|
||||
if (path === '.') return '/';
|
||||
else return path;
|
||||
|
|
@ -39,3 +40,11 @@ export const equals = (arr1: any, arr2: any) => {
|
|||
|
||||
return arr1.every((v, i) => v === arr2[i]);
|
||||
};
|
||||
|
||||
export const isParent = (parent: string, child: string): boolean => {
|
||||
if (child === parent) return false;
|
||||
if (parent === '/') parent = '';
|
||||
if (child === '/') child = '';
|
||||
const parentTokens = parent.split('/').filter((i) => i.length);
|
||||
return parentTokens.every((t, i) => child.split('/')[i] === t);
|
||||
};
|
||||
|
|
|
|||
38
src/vault-handler.ts
Normal file
38
src/vault-handler.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { updateCount } from 'folder-count';
|
||||
import FileExplorerNoteCount from 'main';
|
||||
import { getParentPath } from 'misc';
|
||||
import { App, debounce, TAbstractFile, Vault } from 'obsidian';
|
||||
|
||||
export class VaultHandler {
|
||||
waitingList: string[] = [];
|
||||
get app(): App {
|
||||
return this.plugin.app;
|
||||
}
|
||||
get vault(): Vault {
|
||||
return this.plugin.app.vault;
|
||||
}
|
||||
plugin: FileExplorerNoteCount;
|
||||
constructor(plugin: FileExplorerNoteCount) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
update = debounce(
|
||||
() => updateCount(this.waitingList, this.plugin),
|
||||
500,
|
||||
true,
|
||||
);
|
||||
|
||||
handler = (...args: (string | TAbstractFile)[]) => {
|
||||
for (const arg of args) {
|
||||
const path = arg instanceof TAbstractFile ? arg.path : arg;
|
||||
this.waitingList.push(getParentPath(path) ?? '/');
|
||||
}
|
||||
this.update();
|
||||
};
|
||||
|
||||
registerVaultEvent = () => {
|
||||
this.plugin.registerEvent(this.vault.on('create', this.handler));
|
||||
this.plugin.registerEvent(this.vault.on('rename', this.handler));
|
||||
this.plugin.registerEvent(this.vault.on('delete', this.handler));
|
||||
};
|
||||
}
|
||||
Loading…
Reference in a new issue