diff --git a/surfsense_obsidian/src/main.ts b/surfsense_obsidian/src/main.ts index e9dda860b..db3ef8392 100644 --- a/surfsense_obsidian/src/main.ts +++ b/surfsense_obsidian/src/main.ts @@ -20,7 +20,7 @@ export default class SurfSensePlugin extends Plugin { queue!: PersistentQueue; engine!: SyncEngine; private statusBar: StatusBar | null = null; - lastStatus: StatusState = { kind: "idle", queueDepth: 0 }; + lastStatus: StatusState = { kind: "needs-setup", queueDepth: 0 }; serverCapabilities: string[] = []; private settingTab: SurfSenseSettingTab | null = null; private statusListeners = new Set<() => void>(); @@ -265,6 +265,9 @@ export default class SurfSensePlugin extends Plugin { async saveSettings() { await this.saveData(this.settings); + // Ensures the indicator reacts to settings edits (token paste, search-space pick) + // without waiting for the next sync trigger. + this.engine?.refreshStatus(); } /** diff --git a/surfsense_obsidian/src/settings.ts b/surfsense_obsidian/src/settings.ts index d8179d592..0f2c0d2e7 100644 --- a/surfsense_obsidian/src/settings.ts +++ b/surfsense_obsidian/src/settings.ts @@ -20,6 +20,8 @@ export class SurfSenseSettingTab extends PluginSettingTab { private readonly plugin: SurfSensePlugin; private searchSpaces: SearchSpace[] = []; private loadingSpaces = false; + private connectionIndicator: HTMLElement | null = null; + private readonly onStatusChange = (): void => this.updateConnectionIndicator(); constructor(app: App, plugin: SurfSensePlugin) { super(app, plugin); @@ -29,6 +31,7 @@ export class SurfSenseSettingTab extends PluginSettingTab { display(): void { const { containerEl } = this; containerEl.empty(); + this.plugin.onStatusChange(this.onStatusChange); const settings = this.plugin.settings; @@ -279,13 +282,26 @@ export class SurfSenseSettingTab extends PluginSettingTab { ); } + hide(): void { + this.plugin.offStatusChange(this.onStatusChange); + this.connectionIndicator = null; + } + private renderConnectionHeading(containerEl: HTMLElement): void { const heading = new Setting(containerEl).setName("Connection").setHeading(); heading.nameEl.addClass("surfsense-connection-heading"); - const indicator = heading.nameEl.createSpan({ + this.connectionIndicator = heading.nameEl.createSpan({ cls: "surfsense-connection-indicator", }); + this.updateConnectionIndicator(); + } + + private updateConnectionIndicator(): void { + const indicator = this.connectionIndicator; + if (!indicator) return; const visual = STATUS_VISUALS[this.plugin.lastStatus.kind]; + indicator.empty(); + indicator.removeClass("surfsense-connection-indicator--err"); if (visual.isError) { indicator.addClass("surfsense-connection-indicator--err"); } diff --git a/surfsense_obsidian/src/status-visuals.ts b/surfsense_obsidian/src/status-visuals.ts index b7bd2e350..78a877587 100644 --- a/surfsense_obsidian/src/status-visuals.ts +++ b/surfsense_obsidian/src/status-visuals.ts @@ -16,7 +16,8 @@ export const STATUS_VISUALS: Record = { idle: { icon: "check-circle", label: "Synced", isError: false }, syncing: { icon: "refresh-ccw", label: "Syncing", isError: false }, queued: { icon: "clock", label: "Queued", isError: false }, + "needs-setup": { icon: "cloud-off", label: "Setup required", isError: false }, offline: { icon: "wifi-off", label: "Offline", isError: false }, - "auth-error": { icon: "user-x", label: "Reauthenticate", isError: true }, + "auth-error": { icon: "alert-circle", label: "Reauthenticate", isError: true }, error: { icon: "alert-circle", label: "Error", isError: true }, }; diff --git a/surfsense_obsidian/src/sync-engine.ts b/surfsense_obsidian/src/sync-engine.ts index 5ef41b633..e027b64c0 100644 --- a/surfsense_obsidian/src/sync-engine.ts +++ b/surfsense_obsidian/src/sync-engine.ts @@ -46,6 +46,7 @@ export interface SyncEngineDeps { export interface SyncEngineSettings { vaultId: string; + apiToken: string; connectorId: number | null; searchSpaceId: number | null; includeFolders: string[]; @@ -103,7 +104,7 @@ export class SyncEngine { this.handleStartupError(err); return; } - this.setStatus("idle", "Pick a search space in settings to start syncing."); + this.setStatus("idle"); return; } @@ -123,7 +124,7 @@ export class SyncEngine { async ensureConnected(): Promise { const settings = this.deps.getSettings(); if (!settings.searchSpaceId) { - this.setStatus("idle", "Pick a search space in settings."); + this.setStatus("idle"); return false; } this.setStatus("syncing", "Connecting to SurfSense"); @@ -500,10 +501,34 @@ export class SyncEngine { // ---- status helpers --------------------------------------------------- + /** + * Recomputes status from settings + queue depth. Call from main.ts after + * settings change so the indicator reacts to token paste / search-space + * pick without waiting for the next sync trigger. + */ + refreshStatus(): void { + this.setStatus(this.queueStatusKind(), this.statusDetail()); + } + private setStatus(kind: StatusKind, detail?: string): void { + // Errors carry meaningful signal; only "happy" kinds get downgraded + // to needs-setup when prerequisites are missing. + if (kind !== "auth-error" && kind !== "offline" && kind !== "error") { + const s = this.deps.getSettings(); + if (!s.apiToken || !s.searchSpaceId || !s.connectorId) { + kind = "needs-setup"; + detail = this.setupHint(s); + } + } this.deps.setStatus({ kind, detail, queueDepth: this.deps.queue.size }); } + private setupHint(s: SyncEngineSettings): string { + if (!s.apiToken) return "Paste your API token in settings."; + if (!s.searchSpaceId) return "Pick a search space in settings."; + return "Connecting…"; + } + private queueStatusKind(): StatusKind { if (this.deps.queue.size > 0) return "queued"; return "idle"; diff --git a/surfsense_obsidian/src/types.ts b/surfsense_obsidian/src/types.ts index 2cb040463..192d34dc8 100644 --- a/surfsense_obsidian/src/types.ts +++ b/surfsense_obsidian/src/types.ts @@ -190,6 +190,7 @@ export type StatusKind = | "idle" | "syncing" | "queued" + | "needs-setup" | "offline" | "auth-error" | "error";