From 4b7549e4a659ac524ac278190e9ad20ecd04333f Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 17 Feb 2026 12:53:36 -0800 Subject: [PATCH] Dont trigger notification for regular save "echoes" --- .../document-detail.component.spec.ts | 23 ++++++++++++++++ .../document-detail.component.ts | 26 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts index 088b78d9c..925074a71 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.spec.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.spec.ts @@ -1239,6 +1239,7 @@ describe('DocumentDetailComponent', () => { it('should queue incoming update while network is active and flush after', () => { initNormally() const loadSpy = jest.spyOn(component as any, 'loadDocument') + const toastSpy = jest.spyOn(toastService, 'showInfo') component.networkActive = true ;(component as any).handleIncomingDocumentUpdated({ @@ -1252,6 +1253,28 @@ describe('DocumentDetailComponent', () => { ;(component as any).flushPendingIncomingUpdate() expect(loadSpy).toHaveBeenCalledWith(component.documentId, true) + expect(toastSpy).toHaveBeenCalledWith( + 'Document reloaded with latest changes.' + ) + }) + + it('should ignore queued incoming update matching local save modified', () => { + initNormally() + const loadSpy = jest.spyOn(component as any, 'loadDocument') + const toastSpy = jest.spyOn(toastService, 'showInfo') + + component.networkActive = true + ;(component as any).lastLocalSaveModified = '2026-02-17T00:00:00+00:00' + ;(component as any).handleIncomingDocumentUpdated({ + document_id: component.documentId, + modified: '2026-02-17T00:00:00+00:00', + }) + + component.networkActive = false + ;(component as any).flushPendingIncomingUpdate() + + expect(loadSpy).not.toHaveBeenCalled() + expect(toastSpy).not.toHaveBeenCalled() }) it('should change preview element by render type', () => { diff --git a/src-ui/src/app/components/document-detail/document-detail.component.ts b/src-ui/src/app/components/document-detail/document-detail.component.ts index 029799243..9e117d0ff 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.ts +++ b/src-ui/src/app/components/document-detail/document-detail.component.ts @@ -271,6 +271,7 @@ export class DocumentDetailComponent docChangeNotifier: Subject = new Subject() private incomingUpdateModal: NgbModalRef private pendingIncomingUpdate: IncomingDocumentUpdate + private lastLocalSaveModified: string | null = null requiresPassword: boolean = false password: string @@ -496,9 +497,16 @@ export class DocumentDetailComponent this.handleIncomingDocumentUpdated(pendingUpdate) } + private getModifiedRawValue(modified: string | Date): string | null { + if (!modified) return null + if (typeof modified === 'string') return modified + return modified.toISOString() + } + private loadDocument(documentId: number, forceRemote: boolean = false): void { this.closeIncomingUpdateModal() this.pendingIncomingUpdate = null + this.lastLocalSaveModified = null this.previewUrl = this.documentsService.getPreviewUrl(documentId) this.updatePdfSource() this.http @@ -603,6 +611,18 @@ export class DocumentDetailComponent this.pendingIncomingUpdate = data return } + // If modified timestamp of the incoming update is the same as the last local save, + // we assume this update is from our own save and dont notify + const incomingModified = this.getModifiedRawValue(data.modified) + if ( + incomingModified && + this.lastLocalSaveModified && + incomingModified === this.lastLocalSaveModified + ) { + this.lastLocalSaveModified = null + return + } + this.lastLocalSaveModified = null if (this.openDocumentService.isDirty(this.document)) { this.showIncomingUpdateModal(data.modified) @@ -1069,6 +1089,9 @@ export class DocumentDetailComponent .subscribe({ next: (docValues) => { this.closeIncomingUpdateModal() + this.lastLocalSaveModified = this.getModifiedRawValue( + docValues.modified + ) // in case data changed while saving eg removing inbox_tags this.documentForm.patchValue(docValues) const newValues = Object.assign({}, this.documentForm.value) @@ -1095,6 +1118,7 @@ export class DocumentDetailComponent }, error: (error) => { this.networkActive = false + this.lastLocalSaveModified = null const canEdit = this.permissionsService.currentUserHasObjectPermissions( PermissionAction.Change, @@ -1155,6 +1179,7 @@ export class DocumentDetailComponent this.error = null this.networkActive = false this.pendingIncomingUpdate = null + this.lastLocalSaveModified = null if (closeResult && updateResult && nextDocId) { this.router.navigate(['documents', nextDocId]) this.titleInput?.focus() @@ -1162,6 +1187,7 @@ export class DocumentDetailComponent }, error: (error) => { this.networkActive = false + this.lastLocalSaveModified = null this.error = error.error this.toastService.showError($localize`Error saving document`, error) this.flushPendingIncomingUpdate()