feat: add support for blacklist function

Allow to specify the folder blacklist and the regex blacklist of note name.

Resolve: #7
This commit is contained in:
dragonish 2025-04-03 22:12:50 +08:00
parent ca1026eb97
commit c013db2e56
No known key found for this signature in database
GPG key ID: 6F42FA9E807A5177
4 changed files with 229 additions and 7 deletions

View file

@ -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;
}
}

21
components/suggest.ts Normal file
View file

@ -0,0 +1,21 @@
import { App, AbstractInputSuggest } from "obsidian";
export class FolderSuggest extends AbstractInputSuggest<string> {
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);
}
}

179
main.ts
View file

@ -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();
});
});
}
}

View file

@ -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;
});
});