khabaroff-studio_obsidian-w.../plugin/settings_tab.ts
Sergei Khabarov 437aa1639e Fix all ObsidianReviewBot ESLint issues for plugin directory approval
- Replace fetch with Obsidian requestUrl in ack_handler, polling_handler, settings_tab
- Remove unnecessary try/catch wrapper in ack_handler
- Use instanceof TFile instead of type casting in file_handler
- Replace any types with unknown/Record in json-formatter and file_handler
- Fix hasOwnProperty: use "in" operator and Object.prototype.hasOwnProperty.call()
- Fix timer types: NodeJS.Timeout → ReturnType<typeof setTimeout/setInterval>
- Fix unhandled promises: add void operator for fire-and-forget calls
- Remove async from SSEHandler.disconnect() (no await inside)
- Change console.log to console.debug in main.ts
- Fix sentence case in UI text, remove "settings" from heading
- Remove unused variables (processingTime, advancedSetting)
- Fix no-unsafe-* errors: type loadData(), replace .bind() with arrows
- Fix no-base-to-string in json-formatter
- Add eslint with eslint-plugin-obsidianmd for local validation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:22:16 +04:00

250 lines
7.1 KiB
TypeScript

/**
* settings_tab.ts - Plugin Settings UI (v3.0 - Simplified)
*
* Implements Progressive Disclosure principle:
* - Only 2 essential fields visible by default
* - Advanced settings collapsed
* - Smart defaults that "just work"
*/
import { App, Notice, PluginSettingTab, Setting, requestUrl } from "obsidian";
import type ObsidianWebhooksPlugin from "./main";
export class WebhookSettingTab extends PluginSettingTab {
plugin: ObsidianWebhooksPlugin;
private advancedExpanded = false;
constructor(app: App, plugin: ObsidianWebhooksPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
// Status indicator at the top (most important)
this.renderStatusIndicator(containerEl);
// Client Key (only essential setting)
this.renderClientKey(containerEl);
// Test Connection button
this.renderTestConnection(containerEl);
// Advanced Settings (collapsed by default)
this.renderAdvancedSettings(containerEl);
}
private renderStatusIndicator(containerEl: HTMLElement): void {
const status = this.plugin.connectionStatus;
let className = "webhook-status-error";
let message = status.message;
if (status.state === "connected") {
className = "webhook-status-connected";
message = "Connected";
} else if (status.state === "connecting") {
className = "webhook-status-warning";
} else if (status.message.includes("Reconnecting")) {
className = "webhook-status-warning";
}
const statusDiv = containerEl.createDiv({
cls: `webhook-status-banner ${className}`,
});
statusDiv.createEl("span", {
text: message,
cls: "webhook-status-text",
});
}
private renderClientKey(containerEl: HTMLElement): void {
new Setting(containerEl)
.setName("Client key")
.setDesc("Get your key from the dashboard")
.addText((text) =>
text
.setPlaceholder("Paste your client key")
.setValue(this.plugin.settings.clientKey)
.onChange(async (value) => {
this.plugin.settings.clientKey = value.trim();
await this.plugin.saveSettings();
})
);
}
private renderTestConnection(containerEl: HTMLElement): void {
const setting = new Setting(containerEl)
.setName("Test connection")
.setDesc("Verify your client key and server connection")
.addButton((button) => {
button
.setButtonText("Test")
.onClick(async () => {
button.setDisabled(true);
button.setButtonText("Testing...");
await this.testConnection(setting.settingEl);
button.setDisabled(false);
button.setButtonText("Test");
});
});
}
private async testConnection(settingEl: HTMLElement): Promise<void> {
// Remove previous result if exists (search in parent since result is a sibling)
const parent = settingEl.parentElement;
const existingResult = parent?.querySelector(".webhook-test-result");
if (existingResult) {
existingResult.remove();
}
const clientKey = this.plugin.settings.clientKey;
if (!clientKey) {
const resultDiv = this.createResultDiv(settingEl, "test-error");
resultDiv.textContent = "Enter your client key first";
return;
}
const resultDiv = this.createResultDiv(settingEl);
try {
const startTime = Date.now();
const response = await requestUrl({
url: `${this.plugin.settings.serverUrl}/test/${clientKey}`,
method: "POST",
throw: false,
});
if (response.status < 200 || response.status >= 300) {
const errorMsg = response.status === 401 || response.status === 404
? "Invalid client key"
: `Server error: ${response.status}`;
resultDiv.textContent = `Test failed: ${errorMsg}`;
resultDiv.classList.add("test-error");
return;
}
const latency = Date.now() - startTime;
resultDiv.textContent = `Test passed! Server responded in ${latency}ms`;
resultDiv.classList.add("test-success");
// Auto-hide result after 10 seconds
setTimeout(() => {
resultDiv.remove();
}, 10000);
} catch (error) {
const errorMsg = error instanceof Error ? error.message : "Network error";
resultDiv.textContent = `Test failed: ${errorMsg}`;
resultDiv.classList.add("test-error");
}
}
private createResultDiv(settingEl: HTMLElement, ...classes: string[]): HTMLElement {
const div = document.createElement("div");
div.className = ["webhook-test-result", ...classes].join(" ");
settingEl.insertAdjacentElement("afterend", div);
return div;
}
private renderAdvancedSettings(containerEl: HTMLElement): void {
// Collapsible Advanced Settings section
new Setting(containerEl)
.setName("Advanced")
.setHeading()
.addExtraButton((button) => {
button
.setIcon(this.advancedExpanded ? "chevron-down" : "chevron-right")
.setTooltip(this.advancedExpanded ? "Collapse" : "Expand")
.onClick(() => {
this.advancedExpanded = !this.advancedExpanded;
this.display(); // Re-render
});
});
if (!this.advancedExpanded) {
return;
}
// Server URL (for self-hosted setups)
new Setting(containerEl)
.setName("Server URL")
.setDesc("Change only for self-hosted setup")
.addText((text) =>
text
.setPlaceholder("https://your-server.example.com")
.setValue(this.plugin.settings.serverUrl)
.onChange(async (value) => {
// Normalize: remove trailing slash
this.plugin.settings.serverUrl = value.trim().replace(/\/$/, "");
await this.plugin.saveSettings();
})
);
// Auto-reconnect
new Setting(containerEl)
.setName("Auto-reconnect")
.setDesc("Automatically connect when plugin loads")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.autoConnect)
.onChange(async (value) => {
this.plugin.settings.autoConnect = value;
await this.plugin.saveSettings();
})
);
// Write mode
new Setting(containerEl)
.setName("Write mode")
.setDesc("How to write content to files")
.addDropdown((dropdown) =>
dropdown
.addOption("append", "Append to end")
.addOption("overwrite", "Overwrite file")
.setValue(this.plugin.settings.defaultMode)
.onChange(async (value) => {
this.plugin.settings.defaultMode = value as "append" | "overwrite";
await this.plugin.saveSettings();
})
);
// Debug logging
new Setting(containerEl)
.setName("Debug logging")
.setDesc("Enable console output for troubleshooting")
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enableDebugLogging)
.onChange(async (value) => {
this.plugin.settings.enableDebugLogging = value;
await this.plugin.saveSettings();
})
);
// Quick actions
new Setting(containerEl)
.setName("Quick actions")
.setHeading();
// Open Dashboard button
new Setting(containerEl)
.setName("User dashboard")
.setDesc("View your keys and webhook logs")
.addButton((button) => {
button
.setButtonText("Open dashboard")
.onClick(() => {
const clientKey = this.plugin.settings.clientKey;
if (!clientKey) {
new Notice("Please configure your client key first");
return;
}
window.open(`${this.plugin.settings.serverUrl}/dashboard`, "_blank");
});
});
}
}