refactor: split settings UI into settingsTab.ts to keep settings.ts Node-pure

This commit is contained in:
Etai Solomon 2026-04-21 11:22:15 +03:00
parent 808dd4259b
commit dea5fd83e3
5 changed files with 68 additions and 126 deletions

View file

@ -1,6 +1,7 @@
import { Plugin } from "obsidian";
import { DEFAULT_SETTINGS, PersistedData, PluginSettings } from "./types";
import { normalizeSettings, TagFinderSettingTab } from "./settings";
import { normalizeSettings } from "./settings";
import { TagFinderSettingTab } from "./settingsTab";
import { RecentTags, RecentTagsStore } from "./recentTags";
import { TagSuggestModal } from "./tagSuggestModal";
import { NoteSuggestModal } from "./noteSuggestModal";

View file

@ -1,6 +1,4 @@
import { App, PluginSettingTab, Setting, Notice } from "obsidian";
import { DEFAULT_SETTINGS, EmptyStateMode, PluginSettings } from "./types";
import type TagFinderPlugin from "./main";
const VALID_MODES: EmptyStateMode[] = ["recent-then-usage", "usage", "alphabetical", "blank"];
@ -26,69 +24,3 @@ export function normalizeSettings(raw: unknown): PluginSettings {
return { emptyStateMode, enableQuickSwitcherHook, recentLimit };
}
// --- Settings tab UI (uses Obsidian runtime — not unit-tested) ---
export class TagFinderSettingTab extends PluginSettingTab {
constructor(app: App, private plugin: TagFinderPlugin) {
super(app, plugin);
}
display(): void {
const { containerEl, plugin } = this;
containerEl.empty();
new Setting(containerEl)
.setName("Initial sort")
.setDesc("Order of tags before you start typing.")
.addDropdown(dd =>
dd
.addOptions({
"recent-then-usage": "Recent, then most-used",
"usage": "Most-used",
"alphabetical": "Alphabetical",
"blank": "Empty until typing",
})
.setValue(plugin.settings.emptyStateMode)
.onChange(async value => {
plugin.settings.emptyStateMode = value as typeof plugin.settings.emptyStateMode;
await plugin.savePluginData();
}),
);
new Setting(containerEl)
.setName("Integrate with Quick Switcher")
.setDesc("Open Tag Finder when the Quick Switcher input starts with '#'.")
.addToggle(t =>
t.setValue(plugin.settings.enableQuickSwitcherHook).onChange(async value => {
plugin.settings.enableQuickSwitcherHook = value;
await plugin.savePluginData();
plugin.refreshQuickSwitcherHook();
}),
);
new Setting(containerEl)
.setName("Recent tags to remember")
.setDesc("0 disables recent entirely.")
.addSlider(s =>
s
.setLimits(0, 30, 1)
.setValue(plugin.settings.recentLimit)
.setDynamicTooltip()
.onChange(async value => {
plugin.settings.recentLimit = value;
plugin.recent.setLimit(value);
await plugin.savePluginData();
}),
);
new Setting(containerEl)
.setName("Recent tags")
.addButton(b =>
b.setButtonText("Clear recent tags").onClick(async () => {
await plugin.recent.clear();
new Notice("Recent tags cleared.");
}),
);
}
}

66
src/settingsTab.ts Normal file
View file

@ -0,0 +1,66 @@
import { App, PluginSettingTab, Setting, Notice } from "obsidian";
import type TagFinderPlugin from "./main";
export class TagFinderSettingTab extends PluginSettingTab {
constructor(app: App, private plugin: TagFinderPlugin) {
super(app, plugin);
}
display(): void {
const { containerEl, plugin } = this;
containerEl.empty();
new Setting(containerEl)
.setName("Initial sort")
.setDesc("Order of tags before you start typing.")
.addDropdown(dd =>
dd
.addOptions({
"recent-then-usage": "Recent, then most-used",
"usage": "Most-used",
"alphabetical": "Alphabetical",
"blank": "Empty until typing",
})
.setValue(plugin.settings.emptyStateMode)
.onChange(async value => {
plugin.settings.emptyStateMode = value as typeof plugin.settings.emptyStateMode;
await plugin.savePluginData();
}),
);
new Setting(containerEl)
.setName("Integrate with Quick Switcher")
.setDesc("Open Tag Finder when the Quick Switcher input starts with '#'.")
.addToggle(t =>
t.setValue(plugin.settings.enableQuickSwitcherHook).onChange(async value => {
plugin.settings.enableQuickSwitcherHook = value;
await plugin.savePluginData();
plugin.refreshQuickSwitcherHook();
}),
);
new Setting(containerEl)
.setName("Recent tags to remember")
.setDesc("0 disables recent entirely.")
.addSlider(s =>
s
.setLimits(0, 30, 1)
.setValue(plugin.settings.recentLimit)
.setDynamicTooltip()
.onChange(async value => {
plugin.settings.recentLimit = value;
plugin.recent.setLimit(value);
await plugin.savePluginData();
}),
);
new Setting(containerEl)
.setName("Recent tags")
.addButton(b =>
b.setButtonText("Clear recent tags").onClick(async () => {
await plugin.recent.clear();
new Notice("Recent tags cleared.");
}),
);
}
}

View file

@ -1,51 +0,0 @@
// Minimal runtime stub for the `obsidian` package in tests.
// The real package ships only type declarations, so any code path that
// imports runtime values from "obsidian" needs an alias during tests.
export class PluginSettingTab {
app: unknown;
plugin: unknown;
containerEl: { empty(): void };
constructor(app: unknown, plugin: unknown) {
this.app = app;
this.plugin = plugin;
this.containerEl = { empty: () => {} };
}
display(): void {}
hide(): void {}
}
export class Setting {
constructor(_containerEl: unknown) {}
setName(_v: string) { return this; }
setDesc(_v: string) { return this; }
addDropdown(_cb: (d: unknown) => unknown) { return this; }
addToggle(_cb: (t: unknown) => unknown) { return this; }
addSlider(_cb: (s: unknown) => unknown) { return this; }
addButton(_cb: (b: unknown) => unknown) { return this; }
}
export class Notice {
constructor(_message: string) {}
}
export class Plugin {
app: unknown;
constructor() { this.app = {}; }
async loadData() { return null; }
async saveData(_data: unknown) {}
addCommand(_cmd: unknown) {}
addSettingTab(_tab: unknown) {}
async onload() {}
async onunload() {}
}
export class SuggestModal<T> {
app: unknown;
constructor(app: unknown) { this.app = app; }
setPlaceholder(_v: string) {}
open() {}
close() {}
}
export class App {}
export class TFile {}

View file

@ -1,14 +1,8 @@
import { defineConfig } from "vitest/config";
import { resolve } from "node:path";
export default defineConfig({
test: {
include: ["tests/**/*.test.ts"],
environment: "node",
},
resolve: {
alias: {
obsidian: resolve(__dirname, "tests/__mocks__/obsidian.ts"),
},
},
});