mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-05 18:20:39 -08:00
fix: prevent echo by exclude title
This commit is contained in:
parent
1c88aca19b
commit
86aa838d24
3 changed files with 93 additions and 1 deletions
|
|
@ -27,6 +27,8 @@ export class InverseFilesIndex {
|
|||
private mainExcludedFiles: Set<string> = new Set();
|
||||
/** Temporarily excluded files for each sub-wiki watcher (by absolute path) */
|
||||
private subWikiExcludedFiles: Map<string, Set<string>> = new Map();
|
||||
/** Temporarily excluded tiddler titles during save/delete operations */
|
||||
private excludedTiddlerTitles: Set<string> = new Set();
|
||||
|
||||
/**
|
||||
* Set the main wiki path
|
||||
|
|
@ -227,4 +229,48 @@ export class InverseFilesIndex {
|
|||
const excluded = this.subWikiExcludedFiles.get(subWikiId);
|
||||
return excluded ? Array.from(excluded) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tiddler title to the exclusion list
|
||||
* @param title Tiddler title to exclude
|
||||
*/
|
||||
excludeTiddlerTitle(title: string): void {
|
||||
this.excludedTiddlerTitles.add(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a tiddler title from the exclusion list
|
||||
* @param title Tiddler title to include
|
||||
*/
|
||||
includeTiddlerTitle(title: string): void {
|
||||
this.excludedTiddlerTitles.delete(title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tiddler title is currently excluded
|
||||
* @param title Tiddler title
|
||||
* @returns True if title is excluded
|
||||
*/
|
||||
isTiddlerTitleExcluded(title: string): boolean;
|
||||
/**
|
||||
* Filter out excluded tiddlers from a changes object
|
||||
* @param changes Changed tiddlers object
|
||||
* @returns Filtered changes with excluded tiddlers removed
|
||||
*/
|
||||
isTiddlerTitleExcluded(changes: Record<string, unknown>): Record<string, unknown>;
|
||||
isTiddlerTitleExcluded(input: string | Record<string, unknown>): boolean | Record<string, unknown> {
|
||||
// Single title check
|
||||
if (typeof input === 'string') {
|
||||
return this.excludedTiddlerTitles.has(input);
|
||||
}
|
||||
|
||||
// Filter changes object
|
||||
const filteredChanges: Record<string, unknown> = {};
|
||||
for (const title in input) {
|
||||
if (input[title] && !this.excludedTiddlerTitles.has(title)) {
|
||||
filteredChanges[title] = input[title];
|
||||
}
|
||||
}
|
||||
return filteredChanges;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,10 @@ export class WatchFileSystemAdaptor extends FileSystemAdaptor {
|
|||
* Can be used with callback (legacy) or as async/await
|
||||
*/
|
||||
override async saveTiddler(tiddler: Tiddler, callback?: IFileSystemAdaptorCallback, options?: { tiddlerInfo?: Record<string, unknown> }): Promise<void> {
|
||||
const title = tiddler.fields.title;
|
||||
// Exclude title to prevent Wiki change events from being sent to frontend
|
||||
this.inverseFilesIndex.excludeTiddlerTitle(title);
|
||||
|
||||
try {
|
||||
// Get file info to calculate path for watching
|
||||
const fileInfo = await this.getTiddlerFileInfo(tiddler);
|
||||
|
|
@ -104,7 +108,14 @@ export class WatchFileSystemAdaptor extends FileSystemAdaptor {
|
|||
|
||||
// Schedule file re-inclusion after save completes
|
||||
this.scheduleFileInclusion(fileInfo.filepath);
|
||||
|
||||
// Remove title from exclusion after delay
|
||||
setTimeout(() => {
|
||||
this.inverseFilesIndex.includeTiddlerTitle(title);
|
||||
}, FILE_EXCLUSION_CLEANUP_DELAY_MS);
|
||||
} catch (error) {
|
||||
// Clean up title exclusion on error
|
||||
this.inverseFilesIndex.includeTiddlerTitle(title);
|
||||
const errorObject = error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown error');
|
||||
callback?.(errorObject);
|
||||
throw errorObject;
|
||||
|
|
@ -116,9 +127,13 @@ export class WatchFileSystemAdaptor extends FileSystemAdaptor {
|
|||
* Can be used with callback (legacy) or as async/await
|
||||
*/
|
||||
override async deleteTiddler(title: string, callback?: IFileSystemAdaptorCallback, _options?: unknown): Promise<void> {
|
||||
// Exclude title to prevent Wiki change events from being sent to frontend
|
||||
this.inverseFilesIndex.excludeTiddlerTitle(title);
|
||||
|
||||
const fileInfo = this.boot.files[title];
|
||||
|
||||
if (!fileInfo) {
|
||||
this.inverseFilesIndex.includeTiddlerTitle(title);
|
||||
callback?.(null, null);
|
||||
return;
|
||||
}
|
||||
|
|
@ -141,7 +156,14 @@ export class WatchFileSystemAdaptor extends FileSystemAdaptor {
|
|||
|
||||
// Schedule file re-inclusion after deletion completes
|
||||
this.scheduleFileInclusion(fileRelativePath);
|
||||
|
||||
// Remove title from exclusion after delay
|
||||
setTimeout(() => {
|
||||
this.inverseFilesIndex.includeTiddlerTitle(title);
|
||||
}, FILE_EXCLUSION_CLEANUP_DELAY_MS);
|
||||
} catch (error) {
|
||||
// Clean up title exclusion on error
|
||||
this.inverseFilesIndex.includeTiddlerTitle(title);
|
||||
// Schedule file re-inclusion on error to clean up exclusion list
|
||||
this.scheduleFileInclusion(fileRelativePath);
|
||||
const errorObject = error instanceof Error ? error : new Error(typeof error === 'string' ? error : 'Unknown error');
|
||||
|
|
|
|||
|
|
@ -225,7 +225,31 @@ export class IpcServerRoutes {
|
|||
observer.error(new Error(`this.wikiInstance is undefined, maybe something went wrong between waitForIpcServerRoutesAvailable and return new Observable.`));
|
||||
}
|
||||
this.wikiInstance.wiki.addEventListener('change', (changes) => {
|
||||
observer.next(changes);
|
||||
// Filter out changes for tiddlers that are currently being saved/deleted
|
||||
// This prevents echo: backend saves file → change event → frontend syncs → loads old file
|
||||
const syncAdaptor = this.wikiInstance.syncadaptor as {
|
||||
inverseFilesIndex?: {
|
||||
isTiddlerTitleExcluded: ((title: string) => boolean) & ((changes: IChangedTiddlers) => IChangedTiddlers);
|
||||
};
|
||||
} | undefined;
|
||||
|
||||
let filteredChanges: IChangedTiddlers = changes;
|
||||
|
||||
// Try to filter out excluded tiddlers if the method exists
|
||||
if (syncAdaptor?.inverseFilesIndex?.isTiddlerTitleExcluded) {
|
||||
try {
|
||||
filteredChanges = syncAdaptor.inverseFilesIndex.isTiddlerTitleExcluded(changes);
|
||||
} catch (error) {
|
||||
// If filtering fails, send all changes
|
||||
console.error('Failed to filter excluded tiddlers:', error);
|
||||
filteredChanges = changes;
|
||||
}
|
||||
}
|
||||
|
||||
// Send changes if there are any
|
||||
if (filteredChanges && Object.keys(filteredChanges).length > 0) {
|
||||
observer.next(filteredChanges);
|
||||
}
|
||||
});
|
||||
// Log SSE ready every time a new observer subscribes (including after worker restart)
|
||||
// Include timestamp to make each log entry unique for test detection
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue