Fix note sync reliability and Unicode filename support

- Fixed critical bug where notes with empty category had malformed IDs (leading slash)
- Added URL encoding for Czech and Unicode characters in WebDAV paths
- Fixed filename sanitization to preserve Unicode characters (only remove filesystem-unsafe chars)
- Updated note preview to show first non-empty line after title instead of repeating title
- Adjusted default column widths for better proportions (increased window width to 1300px)
- Protection mechanism now works correctly with proper note ID matching
This commit is contained in:
drelich
2026-03-31 09:55:44 +02:00
parent 525413a08a
commit 12b50c2304
5 changed files with 144 additions and 28 deletions

View File

@@ -10,6 +10,8 @@ export class SyncManager {
private syncInProgress: boolean = false;
private statusCallback: ((status: SyncStatus, pendingCount: number) => void) | null = null;
private syncCompleteCallback: (() => void) | null = null;
private recentlyModifiedNotes: Set<number | string> = new Set();
private readonly PROTECTION_WINDOW_MS = 10000;
constructor() {
window.addEventListener('online', () => {
@@ -110,10 +112,13 @@ export class SyncManager {
}
}
// Remove deleted notes from cache
// Remove deleted notes from cache (but protect recently modified notes)
for (const cachedNote of cachedNotes) {
if (!serverMap.has(cachedNote.id)) {
await localDB.deleteNote(cachedNote.id);
// Don't delete notes that were recently created/updated (race condition protection)
if (!this.recentlyModifiedNotes.has(cachedNote.id)) {
await localDB.deleteNote(cachedNote.id);
}
}
}
@@ -236,6 +241,10 @@ export class SyncManager {
this.notifyStatus('syncing', 0);
const note = await this.api.createNoteWebDAV(title, content, category);
await localDB.saveNote(note);
// Protect this note from being deleted by background sync for a short window
this.protectNote(note.id);
this.notifyStatus('idle', 0);
// Trigger background sync to fetch any other changes
@@ -297,8 +306,19 @@ export class SyncManager {
try {
this.notifyStatus('syncing', 0);
const oldId = note.id;
const updatedNote = await this.api.updateNoteWebDAV(note);
// If the note ID changed (due to filename change), delete the old cache entry
if (oldId !== updatedNote.id) {
await localDB.deleteNote(oldId);
}
await localDB.saveNote(updatedNote);
// Protect this note from being deleted by background sync for a short window
this.protectNote(updatedNote.id);
this.notifyStatus('idle', 0);
// Trigger background sync to fetch any other changes
@@ -349,6 +369,10 @@ export class SyncManager {
const movedNote = await this.api.moveNoteWebDAV(note, newCategory);
await localDB.deleteNote(note.id);
await localDB.saveNote(movedNote);
// Protect the moved note from being deleted by background sync
this.protectNote(movedNote.id);
this.notifyStatus('idle', 0);
// Trigger background sync to fetch any other changes
@@ -370,6 +394,14 @@ export class SyncManager {
getOnlineStatus(): boolean {
return this.isOnline;
}
// Protect a note from being deleted during background sync for a short window
private protectNote(noteId: number | string): void {
this.recentlyModifiedNotes.add(noteId);
setTimeout(() => {
this.recentlyModifiedNotes.delete(noteId);
}, this.PROTECTION_WINDOW_MS);
}
}
export const syncManager = new SyncManager();