From f62de8aa08b53fb5c29427cf4e786a6db8f4e586 Mon Sep 17 00:00:00 2001 From: vorotamoroz Date: Sun, 27 Aug 2023 16:12:10 +0900 Subject: [PATCH] Improved: - Performance improvements on LinkFolder - Now works smoothly like the previous version if LinkFolder is not shown. Fixed: - Now LinkFolder should follow the links correctly. - Indirectly referenced notes could be collected correctly. - Outgoing links and incoming links also be. --- TagFolderViewBase.ts | 12 +++-- TagFolderViewComponent.svelte | 24 ++++----- V2TreeFolderComponent.svelte | 47 ++++++++++++++--- main.ts | 98 ++++++++++++++++++++++------------- util.ts | 53 ++++++++++++++++++- 5 files changed, 174 insertions(+), 60 deletions(-) diff --git a/TagFolderViewBase.ts b/TagFolderViewBase.ts index 25a5248..c72b401 100644 --- a/TagFolderViewBase.ts +++ b/TagFolderViewBase.ts @@ -8,6 +8,7 @@ import { VIEW_TYPE_TAGFOLDER, VIEW_TYPE_TAGFOLDER_LINK, VIEW_TYPE_TAGFOLDER_LIST, + type TagFolderSettings, type ViewItem } from "./types"; import { maxDepth, selectedTags } from "./store"; @@ -30,9 +31,10 @@ export abstract class TagFolderViewBase extends ItemView { component: TagFolderViewComponent; plugin: TagFolderPlugin; navigation: false; - async saveSettings() { + async saveSettings(settings: TagFolderSettings) { + this.plugin.settings = { ...this.plugin.settings, ...settings }; await this.plugin.saveSettings(); - this.plugin.updateFileCaches(); + this.plugin.updateFileCaches(); } showOrder(evt: MouseEvent) { const menu = new Menu(); @@ -280,6 +282,7 @@ export abstract class TagFolderViewBase extends ItemView { menu.addItem((item) => item .setTitle(`Open in new tab`) + .setSection("open") .setIcon("lucide-file-plus") .onClick(async () => { app.workspace.openLinkText(path, path, "tab"); @@ -288,6 +291,7 @@ export abstract class TagFolderViewBase extends ItemView { menu.addItem((item) => item .setTitle(`Open to the right`) + .setSection("open") .setIcon("lucide-separator-vertical") .onClick(async () => { app.workspace.openLinkText(path, path, "split"); @@ -307,7 +311,7 @@ export abstract class TagFolderViewBase extends ItemView { menu.addItem((item) => item .setTitle(`Open in new tab`) - .setSection("open") + .setSection("open") .setIcon("lucide-file-plus") .onClick(async () => { app.workspace.openLinkText(path, path, "tab"); @@ -316,7 +320,7 @@ export abstract class TagFolderViewBase extends ItemView { menu.addItem((item) => item .setTitle(`Open to the right`) - .setSection("open") + .setSection("open") .setIcon("lucide-separator-vertical") .onClick(async () => { app.workspace.openLinkText(path, path, "split"); diff --git a/TagFolderViewComponent.svelte b/TagFolderViewComponent.svelte index 3bb40d8..57c8cc7 100644 --- a/TagFolderViewComponent.svelte +++ b/TagFolderViewComponent.svelte @@ -22,7 +22,7 @@ export let vaultName: string = ""; export let title: string = ""; export let tags: string[] = []; - export let saveSettings: () => Promise; + export let saveSettings: (setting: TagFolderSettings) => Promise; export let showMenu: ( evt: MouseEvent, @@ -78,8 +78,8 @@ let onlyFDREnabled = false; tagFolderSetting.subscribe((setting) => { _setting = setting; - outgoingEnabled = _setting?.linkConfig?.outgoing?.enabled ?? false; - incomingEnabled = _setting?.linkConfig?.incoming?.enabled ?? false; + outgoingEnabled = _setting.linkConfig?.outgoing?.enabled ?? false; + incomingEnabled = _setting.linkConfig?.incoming?.enabled ?? false; onlyFDREnabled = _setting.linkShowOnlyFDR; }); let showSearch = false; @@ -110,21 +110,21 @@ let linkIcon = ""; async function switchIncoming() { - _setting.linkConfig.incoming.enabled = + let newSet = { ..._setting }; + newSet.linkConfig.incoming.enabled = !_setting.linkConfig.incoming.enabled; - if (saveSettings) await saveSettings(); - tagFolderSetting.set({ ..._setting }); + if (saveSettings) await saveSettings(newSet); } async function switchOutgoing() { - _setting.linkConfig.outgoing.enabled = + let newSet = { ..._setting }; + newSet.linkConfig.outgoing.enabled = !_setting.linkConfig.outgoing.enabled; - if (saveSettings) await saveSettings(); - tagFolderSetting.set({ ..._setting }); + if (saveSettings) await saveSettings(newSet); } async function switchOnlyFDR() { - _setting.linkShowOnlyFDR = !_setting.linkShowOnlyFDR; - // if (saveSettings) await saveSettings(); - tagFolderSetting.set({ ..._setting }); + let newSet = { ..._setting }; + newSet.linkShowOnlyFDR = !_setting.linkShowOnlyFDR; + if (saveSettings) await saveSettings(newSet); } onMount(() => { diff --git a/V2TreeFolderComponent.svelte b/V2TreeFolderComponent.svelte index d6ac791..645d4fd 100644 --- a/V2TreeFolderComponent.svelte +++ b/V2TreeFolderComponent.svelte @@ -277,6 +277,19 @@ const dLinks = thisInfo.directLinks; tagsAll = tagsAll.filter((e) => dLinks.contains(e)); } + if (!isRoot && _setting.linkCombineOtherTree && thisInfo) { + if (_setting.linkConfig.incoming.enabled) { + tagsAll = [ + ...tagsAll, + ...$allViewItemsByLink + .filter((e) => + e.directLinks.contains(thisName) + ) + .filter((e) => !trail.contains(e.path)) + .map((e) => e.path), + ]; + } + } if (!isRoot) { tagsAll = tagsAll.filter((e) => e != "_unlinked"); } @@ -552,11 +565,30 @@ ); } const dispName = selfInfo?.displayName ?? tag; - const itemCandidates = _setting.linkCombineOtherTree - ? $allViewItemsByLink.filter( - (e) => !trail.contains(e.path) - ) - : _items; + let itemIncomingCandidates = [] as ViewItem[]; + let itemOutgoingCandidates = [] as ViewItem[]; + let itemCandidates = [] as ViewItem[]; + const onlyFDR = _setting.linkShowOnlyFDR; + if (_setting.linkCombineOtherTree) { + if (_setting.linkConfig.outgoing.enabled) { + itemOutgoingCandidates = + $allViewItemsByLink.filter( + (e) => !trail.contains(e.path) + ); + } + if (_setting.linkConfig.incoming.enabled) { + itemIncomingCandidates = $allViewItemsByLink + .filter((e) => e.links.contains(tag)) + .filter((e) => !trail.contains(e.path)); + } + + itemCandidates = [ + ...itemOutgoingCandidates, + ...itemIncomingCandidates, + ]; + } else { + itemCandidates = _items; + } return [ tag, dispName, @@ -566,7 +598,10 @@ e.links.contains("_unlinked") ) : itemCandidates.filter((item) => - isDirectLinked(selfInfo, item) + (onlyFDR ? isDirectLinked : isLinked)( + selfInfo, + item + ) ), // .filter((item) => !trail.contains(item.path)), ] as V2FolderItem; diff --git a/main.ts b/main.ts index fbcc785..92e91bc 100644 --- a/main.ts +++ b/main.ts @@ -415,25 +415,53 @@ export default class TagFolderPlugin extends Plugin { oldFileCache = ""; + + parsedFileCache = new Map(); + + updateFileCachesAll(): boolean { + const filesAll = [...this.app.vault.getMarkdownFiles(), ...this.app.vault.getAllLoadedFiles().filter(e => "extension" in e && e.extension == "canvas") as TFile[]]; + const processFiles = filesAll.filter(file => this.parsedFileCache.get(file.path) ?? 0 != file.stat.mtime); + const cachedLinks = this.app.metadataCache.resolvedLinks; + this.fileCaches = processFiles.map((fileEntry) => { + const [allDirectLinks, allLinks] = this.getLinkView() == null ? [[], []] : parseAllReference(cachedLinks, fileEntry.path, this.settings.linkConfig); + const directLinks = [...allDirectLinks.filter(e => e.endsWith(".md")).map(e => `${e}`)]; + const links = [...allLinks.filter(e => e.endsWith(".md")).map(e => `${e}`)]; + this.parsedFileCache.set(fileEntry.path, fileEntry.stat.mtime); + return { + file: fileEntry, + metadata: this.app.metadataCache.getFileCache(fileEntry), + links: links, + directLinks: directLinks + }; + }); + return this.isFileCacheChanged(); + } + isFileCacheChanged() { + const fileCacheDump = JSON.stringify( + this.fileCaches.map((e) => ({ + path: e.file.path, + links: e.links, + directLinks: e.directLinks, + tags: getAllTags(e.metadata), + })) + ); + if (this.oldFileCache == fileCacheDump) { + return false; + } else { + this.oldFileCache = fileCacheDump; + return true; + } + } + updateFileCaches(diff?: TFile): boolean { + let anyUpdated = false; + if (this.fileCaches.length == 0 || !diff) { - const filesAll = [...this.app.vault.getMarkdownFiles(), ...this.app.vault.getAllLoadedFiles().filter(e => "extension" in e && e.extension == "canvas") as TFile[]]; - const cachedLinks = this.app.metadataCache.resolvedLinks; - this.fileCaches = filesAll.map((fileEntry) => { - const [allDirectLinks, allLinks] = parseAllReference(cachedLinks, fileEntry.path, this.settings.linkConfig); - const directLinks = [...allDirectLinks.filter(e => e.endsWith(".md")).map(e => `${e}`)]; - const links = [...allLinks.filter(e => e.endsWith(".md")).map(e => `${e}`)]; - return { - file: fileEntry, - metadata: this.app.metadataCache.getFileCache(fileEntry), - links: links, - directLinks: directLinks - }; - }); + return this.updateFileCachesAll(); } else { const cachedLinks = this.app.metadataCache.resolvedLinks; - - const [allDirectLinks, allLinks] = parseAllReference(cachedLinks, diff.path, this.settings.linkConfig); + if (this.parsedFileCache.get(diff.path) == diff.stat.mtime) return false; + const [allDirectLinks, allLinks] = this.getLinkView() == null ? [[], []] : parseAllReference(cachedLinks, diff.path, this.settings.linkConfig); const directLinks = [...allDirectLinks.filter(e => e.endsWith(".md")).map(e => `${e}`)]; const links = [...allLinks.filter(e => e.endsWith(".md")).map(e => `${e}`)]; @@ -442,8 +470,11 @@ export default class TagFolderPlugin extends Plugin { ); if (old && JSON.stringify(old?.links) != JSON.stringify(links) && this.getLinkView() != null) { - // Update links - return this.updateFileCaches(); + const files = unique([...old.links, ...links]); + for (const file of files) { + const f = app.vault.getAbstractFileByPath(file); + if (f instanceof TFile) anyUpdated = this.updateFileCaches(f) || anyUpdated; + } } this.fileCaches = this.fileCaches.filter( (fileCache) => fileCache.file.path != diff.path @@ -455,21 +486,7 @@ export default class TagFolderPlugin extends Plugin { directLinks: directLinks }); } - - const fileCacheDump = JSON.stringify( - this.fileCaches.map((e) => ({ - path: e.file.path, - links: e.links, - directLinks: e.directLinks, - tags: getAllTags(e.metadata), - })) - ); - if (this.oldFileCache == fileCacheDump) { - return false; - } else { - this.oldFileCache = fileCacheDump; - return true; - } + return this.isFileCacheChanged() || anyUpdated; } async getItemsList(mode: "tag" | "link"): Promise { @@ -937,6 +954,18 @@ export default class TagFolderPlugin extends Plugin { }); } + async refreshAllViewItems() { + this.parsedFileCache.clear(); + const items = await this.getItemsList("tag"); + const itemsSorted = items.sort(this.compareItems); + this.allViewItems = itemsSorted; + allViewItems.set(this.allViewItems); + + const itemsLink = await this.getItemsList("link"); + const itemsLinkSorted = itemsLink.sort(this.compareItems); + this.allViewItemsByLink = itemsLinkSorted; + allViewItemsByLink.set(this.allViewItemsByLink); + } async loadSettings() { this.settings = Object.assign( {}, @@ -954,10 +983,7 @@ export default class TagFolderPlugin extends Plugin { await this.saveTagInfo(); tagFolderSetting.set(this.settings); this.compareItems = getCompareMethodItems(this.settings); - if (this.allViewItems) { - this.allViewItems = this.allViewItems.sort(this.compareItems); - allViewItems.set(this.allViewItems); - } + this.refreshAllViewItems(); // this.compareTags = getCompareMethodTags(this.settings); } diff --git a/util.ts b/util.ts index 4b04402..84b2ffa 100644 --- a/util.ts +++ b/util.ts @@ -390,27 +390,76 @@ export function parseTagName(thisName: string, _tagInfo: TagInfoDict): [string, return [tagName, tagNameDisp] } +const frCache = new Map(); +const rrCache = new Map(); + + function parseAllForwardReference(metaCache: Record>, filename: string, passed: string[]) { + const key = `${filename}-${passed.join("-")}`; + const allForwardLinks = Object.keys(metaCache?.[filename] ?? {}).map(e => `${e}`).filter(e => !passed.contains(e)); + if (frCache.has(key)) { + const cached = frCache.get(key); + if (cached[1].join("-") != allForwardLinks.join("-")) { + invalidateRefCache(unique([...cached[1], ...allForwardLinks])) + } else { + return cached; + } + } const nextPassed = [...passed, ...allForwardLinks]; let allNextLinks = [] as string[]; for (const link of allForwardLinks) { const [, next] = parseAllForwardReference(metaCache, link, nextPassed); allNextLinks = [...allNextLinks, ...next]; } - return [unique(allForwardLinks), unique([...allForwardLinks, ...allNextLinks])]; + const ret = [unique(allForwardLinks), unique([...allForwardLinks, ...allNextLinks])]; + frCache.set(key, ret); + return ret; } function parseAllReverseReference(metaCache: Record>, filename: string, passed: string[]) { + const key = `${filename}-${passed.join("-")}`; const allReverseLinks = Object.entries((metaCache)).filter(([, links]) => filename in links).map(([name,]) => name).map(e => `${e}`).filter(e => !passed.contains(e)); + + if (rrCache.has(key)) { + const cached = rrCache.get(key); + if (cached[1].join("-") != allReverseLinks.join("-")) { + invalidateRefCache(unique([...cached[1], ...allReverseLinks])) + } else { + return cached; + } + } + const nextPassed = [...passed, ...allReverseLinks]; let allNextLinks = [] as string[]; for (const link of allReverseLinks) { const [, next] = parseAllReverseReference(metaCache, link, nextPassed); allNextLinks = [...allNextLinks, ...next]; } - return [unique(allReverseLinks), unique([...allReverseLinks, ...allNextLinks])]; + const ret = [unique(allReverseLinks), unique([...allReverseLinks, ...allNextLinks])]; + rrCache.set(key, ret); + return ret; +} +export function invalidateRefCache(filenames: string[]) { + const delRCache = [] as string[]; + for (const [key, value] of rrCache) { + if (isIntersect(value[1], filenames)) { + delRCache.push(key); + } + } + const delFCache = [] as string[]; + for (const [key, value] of frCache) { + if (isIntersect(value[1], filenames)) { + delFCache.push(key); + } + } + for (const key of delRCache) { + rrCache.delete(key); + } for (const key of delFCache) { + frCache.delete(key); + } } + export function parseAllReference(metaCache: Record>, filename: string, conf: LinkParseConf): string[][] { const [allDirectForwardLinks, allForwardLinks] = (!conf?.outgoing?.enabled) ? [[], []] : parseAllForwardReference(metaCache, filename, []); const [allDirectReverseLinks, allReverseLinks] = (!conf?.incoming?.enabled) ? [[], []] : parseAllReverseReference(metaCache, filename, []);