diff --git a/common/data.ts b/common/data.ts index cee76d0..94902fc 100644 --- a/common/data.ts +++ b/common/data.ts @@ -62,6 +62,8 @@ interface HeadingDecoratorSettings { export type HeadingPluginSettings = { metadataKeyword: string; + folderBlacklist: string[]; + fileRegexBlacklist: string[]; enabledInReading: boolean; enabledInPreview: boolean; enabledInSource: boolean; @@ -71,6 +73,8 @@ export type HeadingPluginSettings = { export type HeadingPluginData = Omit< HeadingPluginSettings, | "metadataKeyword" + | "folderBlacklist" + | "fileRegexBlacklist" | "enabledInReading" | "readingSettings" | "enabledInOutline" @@ -284,3 +288,25 @@ export function checkEnabledCss( } return null; // default to null if no matching class is found } + +/** + * Converts a string to a regex object. + * @param value The string to convert to a regex. + * @returns A regex object if the string is a valid regex, otherwise null. + */ +export function stringToRegex(value: string) { + const str = value.trim(); + if (!str) { + return null; + } + + try { + const re = new RegExp( + str.substring(1, str.lastIndexOf("/")), + str.substring(str.lastIndexOf("/") + 1) + ); + return re; + } catch { + return null; + } +} diff --git a/components/suggest.ts b/components/suggest.ts new file mode 100644 index 0000000..49785ec --- /dev/null +++ b/components/suggest.ts @@ -0,0 +1,21 @@ +import { App, AbstractInputSuggest } from "obsidian"; + +export class FolderSuggest extends AbstractInputSuggest { + constructor( + public app: App, + public textInputEl: HTMLInputElement | HTMLDivElement + ) { + super(app, textInputEl); + } + + getSuggestions(query: string): string[] { + const folders = this.app.vault.getAllFolders(); + return folders + .map((folder) => folder.path) + .filter((path) => path.toLowerCase().includes(query.toLowerCase())); + } + + renderSuggestion(value: string, el: HTMLElement) { + el.setText(value); + } +} diff --git a/main.ts b/main.ts index e04100f..99b989f 100644 --- a/main.ts +++ b/main.ts @@ -25,6 +25,7 @@ import { diffLevel, getBoolean, checkEnabledCss, + stringToRegex, } from "./common/data"; import { Counter, Querier } from "./common/counter"; import { Heading } from "./common/heading"; @@ -43,6 +44,7 @@ import { updateEditorMode, } from "./components/view"; import { OutlineChildComponent } from "./components/outline"; +import { FolderSuggest } from "./components/suggest"; interface ObsidianEditor extends Editor { cm: EditorView; @@ -54,6 +56,8 @@ interface ObsidianWorkspaceLeaf extends WorkspaceLeaf { const DEFAULT_SETTINGS: HeadingPluginSettings = { metadataKeyword: "heading", + fileRegexBlacklist: [], + folderBlacklist: [], enabledInReading: true, readingSettings: defaultHeadingDecoratorSettings(), enabledInPreview: true, @@ -90,6 +94,7 @@ export default class HeadingPlugin extends Plugin { "reading", context.frontmatter ); + if (metadataEnabled == null) { if ( this.settings.readingSettings.enabledInEachNote != undefined && @@ -97,6 +102,10 @@ export default class HeadingPlugin extends Plugin { ) { return; } + + if (this.getEnabledFromBlacklist(context.sourcePath)) { + return; + } } else if (!metadataEnabled) { return; } @@ -403,12 +412,20 @@ export default class HeadingPlugin extends Plugin { } const frontmatter = fileCache.frontmatter; - const enabled = - this.getEnabledFromFrontmatter("outline", frontmatter) ?? - enabledInEachNote ?? - true; + const metadataEnabled = this.getEnabledFromFrontmatter( + "outline", + frontmatter + ); - if (!enabled) { + if (metadataEnabled == null) { + if (enabledInEachNote != undefined && !enabledInEachNote) { + return; + } + + if (this.getEnabledFromBlacklist(state.file)) { + return; + } + } else if (!metadataEnabled) { return; } @@ -526,14 +543,14 @@ export default class HeadingPlugin extends Plugin { enabledInPreview = this.getEnabledFromFrontmatter("preview", frontmatter) ?? previewSettings.enabledInEachNote ?? - true; + !this.getEnabledFromBlacklist(file.path); } if (_enabledInSource) { enabledInSource = this.getEnabledFromFrontmatter("source", frontmatter) ?? sourceSettings.enabledInEachNote ?? - true; + !this.getEnabledFromBlacklist(file.path); } } } @@ -587,6 +604,29 @@ export default class HeadingPlugin extends Plugin { return null; } + + private getEnabledFromBlacklist(filepath: string): boolean { + for (const folder of this.settings.folderBlacklist) { + if (filepath.startsWith(`${folder}/`)) { + return true; + } + } + + const filename = filepath.substring( + filepath.lastIndexOf("/") + 1, + filepath.lastIndexOf(".") + ); + for (const regexStr of this.settings.fileRegexBlacklist) { + const regex = stringToRegex(regexStr); + if (regex) { + if (regex.test(filename)) { + return true; + } + } + } + + return false; + } } class HeadingSettingTab extends PluginSettingTab { @@ -626,6 +666,24 @@ class HeadingSettingTab extends PluginSettingTab { ); metadataKeywordSetting.descEl.appendChild(metadataKeywordDesc); + //* folderBlacklist + new Setting(containerEl) + .setName("Manage folder blacklist") + .addButton((button) => { + button.setButtonText("Manage").onClick(() => { + this.manageFolderBlacklist(); + }); + }); + + //* fileRegexBlacklist + new Setting(containerEl) + .setName("Manage note name regex blacklist") + .addButton((button) => { + button.setButtonText("Manage").onClick(() => { + this.manageFileRegexBlacklist(); + }); + }); + new Setting(containerEl).setName("Reading view").setHeading(); //* enabledInReading @@ -996,4 +1054,111 @@ class HeadingSettingTab extends PluginSettingTab { }) ); } + + private manageFolderBlacklist() { + const { containerEl } = this; + + containerEl.empty(); + + new Setting(containerEl) + .setName("Manage folder blacklist") + .setHeading() + .addButton((button) => { + button.setButtonText("Back").onClick(() => { + this.display(); + }); + }); + + this.plugin.settings.folderBlacklist.forEach((folder, index) => { + new Setting(containerEl) + .setName(`Folder balcklist ${index + 1}`) + .addText((text) => { + text.setValue(folder).onChange(async (value) => { + this.plugin.settings.folderBlacklist[index] = value; + await this.plugin.saveSettings(); + }); + + const suggest = new FolderSuggest(this.app, text.inputEl); + suggest.onSelect(async (value) => { + text.setValue(value); + this.plugin.settings.folderBlacklist[index] = value; + suggest.close(); + await this.plugin.saveSettings(); + }); + }) + .addButton((button) => { + button + .setButtonText("Delete") + .setWarning() + .onClick(async () => { + this.plugin.settings.folderBlacklist.splice(index, 1); + await this.plugin.saveSettings(); + this.manageFolderBlacklist(); + }); + }); + }); + + new Setting(containerEl).addButton((button) => { + button + .setButtonText("Add") + .setCta() + .setTooltip("Add a new folder to the blacklist") + .onClick(async () => { + this.plugin.settings.folderBlacklist.push(""); + await this.plugin.saveSettings(); + this.manageFolderBlacklist(); + }); + }); + } + + private manageFileRegexBlacklist() { + const { containerEl } = this; + + containerEl.empty(); + + new Setting(containerEl) + .setName("Manage note name regex blacklist") + .setHeading() + .addButton((button) => { + button.setButtonText("Back").onClick(() => { + this.display(); + }); + }); + + this.plugin.settings.fileRegexBlacklist.forEach((regex, index) => { + new Setting(containerEl) + .setName(`Note name regex blacklist ${index + 1}`) + .addText((text) => + text + .setPlaceholder("e.g., /^daily.*/i") + .setValue(regex) + .onChange(async (value) => { + this.plugin.settings.fileRegexBlacklist[index] = value.trim(); + await this.plugin.saveSettings(); + }) + ) + .addButton((button) => { + button + .setButtonText("Delete") + .setWarning() + .onClick(async () => { + this.plugin.settings.fileRegexBlacklist.splice(index, 1); + await this.plugin.saveSettings(); + this.manageFileRegexBlacklist(); + }); + }); + }); + + new Setting(containerEl).addButton((button) => { + button + .setButtonText("Add") + .setCta() + .setTooltip("Add a new note name regex blacklist") + .onClick(async () => { + this.plugin.settings.fileRegexBlacklist.push(""); + await this.plugin.saveSettings(); + this.manageFileRegexBlacklist(); + }); + }); + } } diff --git a/test/common/data.spec.ts b/test/common/data.spec.ts index aaf9002..42ccc17 100644 --- a/test/common/data.spec.ts +++ b/test/common/data.spec.ts @@ -5,6 +5,7 @@ import { compareMarkdownText, getBoolean, checkEnabledCss, + stringToRegex, } from "../../common/data"; describe("common/data", function () { @@ -91,4 +92,13 @@ describe("common/data", function () { expect(checkEnabledCss("disable-reading-heading enable-heading", "reading")) .to.be.false; }); + + it("stringToRegex", function () { + expect(stringToRegex("")).to.be.null; + expect(stringToRegex("abc")).to.be.null; + expect(stringToRegex("/abc/")).to.be.an.instanceof(RegExp); + expect(stringToRegex("/abc/ig")).to.be.an.instanceof(RegExp); + expect(stringToRegex("/a\\/bc/ig")).to.be.an.instanceof(RegExp); + expect(stringToRegex("/abc/a")).to.be.null; + }); });