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
text/typescript
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;
}
}