diff --git a/README.md b/README.md index 7aeee5a..468f266 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,30 @@ Off: 1 X ``` +**Exclude links from files** and **Exclude links to files** allow skipping files during indexing. Both accept regex patterns. If you need several excludes, add them on separate lines. Exclusion is checked only for existing files and only for filename without path. + +For example, if exclude *from* is set to `B`, the plugin won't count any links in this file and the output would be: + +``` +2 [[B]] +1 [[C]] +1 [[X]] +``` + +If exclude *to* is set to `B`, then any links to this file will be ignored, and the output will be: + +``` +2 [[C]] +1 [[X]] +``` + +If both exclude *from* and *to* are set to `B`, the the output will be: + +``` +1 [[C]] +1 [[X]] +``` + ## Compatibility v0.0.1 was developed against Obsidian v0.9.12, but it may work in earlier versions (v0.9.7+). diff --git a/src/main.ts b/src/main.ts index 1eb0272..459b027 100644 --- a/src/main.ts +++ b/src/main.ts @@ -63,7 +63,7 @@ export default class LinkIndexer extends Plugin { const files = this.app.vault.getMarkdownFiles(); files.forEach((f) => { - if (this.isExcludedFrom(f)) return; + if (this.isExcluded(f, preset.excludeFromFilenames)) return; this.grabLinks(uniqueLinks, f, this.app.metadataCache.getFileCache(f).links, preset) if (preset.includeEmbeds) { this.grabLinks(uniqueLinks, f, this.app.metadataCache.getFileCache(f).embeds, preset) @@ -81,15 +81,15 @@ export default class LinkIndexer extends Plugin { } } - isExcludedFrom(f: TFile) { - return this.globalExcludes.includes(f.path); + isExcluded(f: TFile, filenamePatterns: string[]) { + return this.globalExcludes.find((g) => pathEqual(g, f.path)) || filenamePatterns.some((p) => new RegExp(p).test(f.name)); } grabLinks(uniqueLinks: Record, f: TFile, links: ReferenceCache[], preset: UsedLinks) { links?.forEach((l) => { const link = getLinkpath(l.link); const originFile = this.app.metadataCache.getFirstLinkpathDest(link, f.path); - if (preset.nonexistentOnly && originFile) { + if (originFile && (preset.nonexistentOnly || this.isExcluded(originFile, preset.excludeToFilenames))) { return; } const origin = originFile ? originFile.path : link; @@ -113,6 +113,8 @@ class UsedLinks { includeEmbeds = true; linkToFiles = true; nonexistentOnly = false; + excludeToFilenames: string[] = []; + excludeFromFilenames: string[] = []; constructor() { this.name = Date.now().toString(); @@ -213,6 +215,31 @@ class LinkIndexerSettingTab extends PluginSettingTab { await this.saveData({ refreshUI: false }); }) ); + + new Setting(containerEl) + .setName('Exclude links from files') + .setDesc('Expects regex patterns. Checks for filename without path.') + .addTextArea((text) => + text + .setValue(report.excludeFromFilenames.join('\n')) + .onChange(async (value) => { + report.excludeFromFilenames = value.split('\n').filter((v) => v); + await this.saveData({ refreshUI: false }); + }) + ); + + new Setting(containerEl) + .setName('Exclude links to files') + .setDesc('Expects regex patterns. Checks for filename without path.') + .addTextArea((text) => + text + .setValue(report.excludeToFilenames.join('\n')) + .onChange(async (value) => { + report.excludeToFilenames = value.split('\n').filter((v) => v); + await this.saveData({ refreshUI: false }); + }) + ); + const deleteButton = new Setting(containerEl).addButton((extra) => { return extra.setButtonText('Delete preset').onClick(async() => { const index = plugin.settings.usedLinks.findIndex((r) => r.name === report.name); @@ -242,4 +269,17 @@ class LinkIndexerSettingTab extends PluginSettingTab { plugin.reloadSettings(); if (options.refreshUI) this.display(); } -} \ No newline at end of file +} + + +function pathEqual(a: string, b: string) { + if (a === b) return true + + return removeDots(normalizePath(a)) === removeDots(normalizePath(b)) +} + +function removeDots(value: string) { + return value.replace(/\\/g, '/') + .replace(/^\.\//, '') + .replace(/\/\.\//, '/') +}