Skip to content

Commit f4a3d1e

Browse files
authored
Fix editor reloading folder while typing (#300) (#320)
* Fixing autoSave glitch * Test cases for autoSave glitch fix
1 parent 645d373 commit f4a3d1e

2 files changed

Lines changed: 27 additions & 75 deletions

File tree

frontend/src/app/pages/cloud/cloud.component.spec.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -490,27 +490,44 @@ describe('CloudComponent Unsaved Changes Flow', () => {
490490
});
491491
});
492492

493-
it('should transition saveStatus through unsaved -> saving -> saved during autosave', async () => {
493+
it('does not autosave or reload the folder while typing (#300 regression)', () => {
494494
component.showFileEditor = true;
495495
component.newFileName = 'note.md';
496496
component.fileContent = 'new markdown';
497497
component.originalFileContent = '';
498498
component.originalFileName = 'note.md';
499499
component.currentFolder = { path: '/root', name: 'root' } as any;
500500

501-
cloudMock.uploadFile.and.returnValue(of({} as any));
502-
503501
component.onContentChange();
502+
503+
// Dirty state still tracked correctly, with no background save kicked off.
504+
expect(component.saveStatus).toBe('unsaved');
505+
expect(component.isEditorDirty).toBeTrue();
506+
507+
// Well past the old 2s autosave delay — nothing should fire.
508+
jasmine.clock().tick(5000);
509+
510+
expect(cloudMock.uploadFile).not.toHaveBeenCalled();
511+
expect(cloudMock.getFolderByPath).not.toHaveBeenCalled();
504512
expect(component.saveStatus).toBe('unsaved');
513+
expect(component.fileContent).toBe('new markdown');
514+
expect(component.originalFileContent).toBe(''); // unchanged: no autosave wrote it
515+
});
505516

506-
jasmine.clock().tick(2000);
517+
it('still updates the local outline and preview while typing, without saving', () => {
518+
component.showFileEditor = true;
519+
component.newFileName = 'note.md';
520+
component.originalFileName = 'note.md';
521+
component.originalFileContent = '';
522+
component.fileContent = '# New Heading\nbody text';
523+
component.currentFolder = { path: '/root', name: 'root' } as any;
507524

508-
// Wait for async autosave Promise to resolve
509-
await component.autosaveFile();
525+
component.onContentChange();
510526

511-
expect(component.saveStatus).toBe('saved');
512-
expect(component.originalFileContent).toBe('new markdown');
513-
expect(component.isEditorDirty).toBeFalse();
527+
expect(component.outline).toEqual([{ text: 'New Heading', level: 1 }]);
528+
expect(component.previewHtml.toString()).toContain('New Heading');
529+
expect(cloudMock.uploadFile).not.toHaveBeenCalled();
530+
expect(cloudMock.getFolderByPath).not.toHaveBeenCalled();
514531
});
515532
});
516533
});

frontend/src/app/pages/cloud/cloud.component.ts

Lines changed: 1 addition & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@ export class CloudComponent implements OnInit, OnDestroy {
113113
originalFileContent = '';
114114
outline: { text: string; level: number }[] = [];
115115
saveStatus: 'saved' | 'saving' | 'unsaved' = 'saved';
116-
autosaveTimer: ReturnType<typeof setTimeout> | null = null;
117116
editorMode: 'edit' | 'preview' | 'split' = 'edit';
118117
previewHtml: SafeHtml = '';
119118

@@ -1788,78 +1787,14 @@ export class CloudComponent implements OnInit, OnDestroy {
17881787
}
17891788

17901789
onContentChange() {
1791-
if (this.isEditorDirty) {
1792-
this.saveStatus = 'unsaved';
1793-
this.triggerAutosave();
1794-
} else {
1795-
this.saveStatus = 'saved';
1796-
if (this.autosaveTimer) {
1797-
clearTimeout(this.autosaveTimer);
1798-
this.autosaveTimer = null;
1799-
}
1800-
}
1790+
this.saveStatus = this.isEditorDirty ? 'unsaved' : 'saved';
18011791
if (this.isMarkdownFile(this.newFileName)) {
18021792
this.updateOutline();
18031793
this.updatePreview();
18041794
}
18051795
}
18061796

1807-
triggerAutosave() {
1808-
if (this.autosaveTimer) {
1809-
clearTimeout(this.autosaveTimer);
1810-
}
1811-
this.autosaveTimer = setTimeout(() => {
1812-
this.autosaveFile();
1813-
}, 2000);
1814-
}
1815-
1816-
async autosaveFile() {
1817-
const nameToSave = this.newFileName.trim();
1818-
if (!nameToSave || !this.isEditorDirty) return;
1819-
1820-
// Background autosave only saves content edits under existing filename.
1821-
// File renames are handled when the user explicitly clicks Save.
1822-
if (this.editingFile && this.fileContent === this.originalFileContent) {
1823-
return;
1824-
}
1825-
1826-
this.saveStatus = 'saving';
1827-
1828-
try {
1829-
const targetName = this.editingFile ? this.editingFile.name : nameToSave;
1830-
const currentPath = this.getRelativePath(this.currentFolder?.path || '/');
1831-
const fileBlob = new Blob([this.fileContent], { type: 'text/plain' });
1832-
const file = new File([fileBlob], targetName);
1833-
await firstValueFrom(this.cloudService.uploadFile(currentPath, file));
1834-
1835-
if (!this.editingFile) {
1836-
const relativeTarget = this.joinRelativePath(currentPath, targetName);
1837-
this.editingFile = {
1838-
path: relativeTarget,
1839-
name: targetName,
1840-
size: fileBlob.size,
1841-
mimeType: 'text/markdown',
1842-
};
1843-
this.newFileName = targetName;
1844-
this.originalFileName = targetName;
1845-
}
1846-
1847-
this.originalFileContent = this.fileContent;
1848-
this.saveStatus = 'saved';
1849-
this.reloadCurrentFolder();
1850-
} catch (err: unknown) {
1851-
this.saveStatus = 'unsaved';
1852-
console.error('Autosave failed:', err);
1853-
this.toast.error('Autosave failed', this.getErrorMessage(err));
1854-
}
1855-
}
1856-
18571797
closeFileEditor() {
1858-
if (this.autosaveTimer) {
1859-
clearTimeout(this.autosaveTimer);
1860-
this.autosaveTimer = null;
1861-
}
1862-
18631798
this.showFileEditor = false;
18641799
this.editingFile = null;
18651800
this.newFileName = '';

0 commit comments

Comments
 (0)