UNPKG

insta-toc

Version:

Simultaneously generate, update, and maintain a table of contents for your notes in real time.

132 lines (104 loc) 4.18 kB
import type { PluginSettingsManager } from "./settings/PluginSettingManager"; import type { FileKey, FoldKey } from "./types"; interface UiState { tocFoldState: Map<FoldKey, boolean>; tocBlockCollapseState: Map<FileKey, boolean>; } export default class UiStateManager implements UiState { public tocFoldState: Map<FoldKey, boolean> = new Map<FoldKey, boolean>(); public tocBlockCollapseState: Map<FileKey, boolean> = new Map<FileKey, boolean>(); private isPersistedDataDirty = false; private saveTimeout: ReturnType<typeof setTimeout> | null = null; private saveInFlight: Promise<void> | null = null; constructor(private settingsManager: PluginSettingsManager) {} public setPersistedUiState( tocFoldState: Map<FoldKey, boolean>, tocBlockCollapseState: Map<FileKey, boolean> ): void { this.tocFoldState = new Map(tocFoldState); this.tocBlockCollapseState = new Map(tocBlockCollapseState); } public getPersistedUiState(): UiState { return { tocFoldState: new Map(this.tocFoldState), tocBlockCollapseState: new Map(this.tocBlockCollapseState) }; } public getTocFoldState(key: FoldKey): boolean | undefined { return this.tocFoldState.get(key); } public setTocFoldState(key: FoldKey, isCollapsed: boolean): void { if (this.tocFoldState.get(key) === isCollapsed) { return; } this.tocFoldState.set(key, isCollapsed); this.markPersistedDataDirty(); } public getTocBlockCollapsed(sourcePath: FileKey): boolean { return this.tocBlockCollapseState.get(sourcePath) ?? false; } public setTocBlockCollapsed(sourcePath: FileKey, isCollapsed: boolean): void { const current = this.tocBlockCollapseState.get(sourcePath) ?? false; if (current === isCollapsed) { return; } this.tocBlockCollapseState.set(sourcePath, isCollapsed); this.markPersistedDataDirty(); } public async flushPersistedData(): Promise<void> { this.clearScheduledSave(); while (true) { if (this.saveInFlight) { await this.saveInFlight; } if (!this.isPersistedDataDirty) { return; } this.isPersistedDataDirty = false; this.saveInFlight = this.settingsManager.savePersistedData(); try { await this.saveInFlight; } finally { this.saveInFlight = null; } } } public pruneTocFoldStateForPath( sourcePath: string, opts: { replacementFile?: FileKey; activeModernFoldKeys?: Set<FoldKey>; } ): void { let foldStateChanged = false; for (const key of Array.from(this.tocFoldState.keys())) { if (key.startsWith(`${sourcePath}::`) && (opts.replacementFile || !opts.activeModernFoldKeys?.has(key))) { if (opts.replacementFile) { const oldValue = this.getTocFoldState(key) ?? false; const newKey = key.replace(sourcePath, opts.replacementFile) as FoldKey; this.tocFoldState.set(newKey, oldValue); } this.tocFoldState.delete(key); if (!foldStateChanged) foldStateChanged = true; } } if (!foldStateChanged) { return; } this.markPersistedDataDirty(); } private markPersistedDataDirty(): void { this.isPersistedDataDirty = true; this.schedulePersistedDataSave(); } private schedulePersistedDataSave(): void { const timeoutDelay = this.settingsManager.settingsWrapper.settings.updateDelay; this.clearScheduledSave(); this.saveTimeout = setTimeout(async () => { console.log(`Timeout: ${timeoutDelay}`); await this.flushPersistedData(); }, timeoutDelay); } private clearScheduledSave(): void { if (this.saveTimeout === null) { return; } clearTimeout(this.saveTimeout); this.saveTimeout = null; } }