From 2bdca130bbee08f7bf6503e7b538bf1dac6a8ddb Mon Sep 17 00:00:00 2001 From: Nathaniel Paulus Date: Thu, 6 Aug 2026 22:58:16 -0400 Subject: [PATCH] SF-2273 Show add comment button again after saving a note on mobile --- .../translate/editor/editor.component.spec.ts | 88 +++++++++++++++++++ .../app/translate/editor/editor.component.ts | 8 +- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.spec.ts index 81516ca547..a5bdb2391a 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.spec.ts @@ -2943,6 +2943,10 @@ describe('EditorComponent', () => { expect(noteThread.notes[0].ownerRef).toEqual(userId); expect(noteThread.notes[0].content).toEqual(content); + // Close the bottom sheet, which is showing again for the still selected verse + env.clickSegmentRef(segmentRef); + env.wait(); + env.dispose(); })); @@ -2971,6 +2975,84 @@ describe('EditorComponent', () => { env.dispose(); })); + it('shows fab again after saving a note from the bottom sheet on mobile viewport', fakeAsync(() => { + const env = new TestEnvironment(); + env.setProjectUserConfig({ selectedBookNum: 40, selectedChapterNum: 1, selectedSegment: 'verse_1_1' }); + env.setCurrentUser('user04'); + env.wait(); + + // Allow check for mobile viewports to return TRUE + env.breakpointObserver.matchedResult = true; + env.clickSegmentRef('verse_1_2'); + env.insertNoteFab.nativeElement.click(); + env.wait(); + expect(window.getComputedStyle(env.insertNoteFab.nativeElement)['visibility']).toBe('hidden'); + env.component.mobileNoteControl.setValue('mobile note'); + env.saveMobileNoteButton!.click(); + env.wait(); + + // The verse is still selected, so the fab should be available for adding another note to it + expect(env.mobileNoteTextArea).toBeFalsy(); + expect(window.getComputedStyle(env.insertNoteFab.nativeElement)['visibility']).toBe('visible'); + + env.dispose(); + })); + + it('keeps fab visible when the editor loses focus while a verse is still selected', fakeAsync(() => { + const env = new TestEnvironment(); + env.setProjectUserConfig({ selectedBookNum: 40, selectedChapterNum: 1, selectedSegment: 'verse_1_1' }); + env.setCurrentUser('user04'); + env.wait(); + + // Allow check for mobile viewports to return TRUE + env.breakpointObserver.matchedResult = true; + env.clickSegmentRef('verse_1_2'); + env.insertNoteFab.nativeElement.click(); + env.wait(); + env.component.mobileNoteControl.setValue('mobile note'); + env.saveMobileNoteButton!.click(); + env.wait(); + + // On a real device, tapping the fab focuses it, so when the bottom sheet is dismissed it cannot restore + // focus to the hidden fab and the editor stays blurred with no quill selection. Closing the on-screen + // keyboard then fires a window resize. The fab should stay visible for the still selected verse. + env.component.target!.editor!.blur(); + window.dispatchEvent(new Event('resize')); + env.wait(); + expect(window.getComputedStyle(env.insertNoteFab.nativeElement)['visibility']).toBe('visible'); + + env.dispose(); + })); + + it('shows bottom sheet again after a commenter saves a note on mobile viewport', fakeAsync(() => { + const env = new TestEnvironment(); + env.setProjectUserConfig(); + env.setCommenterUser(); + env.routeWithParams({ projectId: 'project01', bookId: 'LUK' }); + env.wait(); + + // Allow check for mobile viewports to return TRUE + env.breakpointObserver.matchedResult = true; + env.clickSegmentRef('verse_1_1'); + env.wait(); + env.insertNoteFabMobile!.click(); + env.wait(); + env.component.mobileNoteControl.setValue('commenter leaving mobile note'); + env.saveMobileNoteButton!.click(); + env.wait(); + + // The verse is still selected, so the bottom sheet should be showing its add comment button again + expect(env.mobileNoteTextArea).toBeFalsy(); + expect(env.bottomSheetVerseReference?.textContent).toEqual('Luke 1:1'); + expect(env.insertNoteFabMobile).toBeTruthy(); + + // Close the bottom sheet + env.clickSegmentRef('verse_1_1'); + env.wait(); + + env.dispose(); + })); + it('shows current selected verse on bottom sheet', fakeAsync(() => { const env = new TestEnvironment(); env.setProjectUserConfig(); @@ -2992,6 +3074,9 @@ describe('EditorComponent', () => { const [, noteThread] = capture(mockedSFProjectService.createNoteThread).last(); expect(noteThread.verseRef).toEqual(fromVerseRef(new VerseRef('LUK 1:1'))); expect(noteThread.notes[0].content).toEqual(content); + // Close the bottom sheet, which is showing again for the still selected verse + env.clickSegmentRef('verse_1_1'); + env.wait(); env.dispose(); })); @@ -3016,6 +3101,9 @@ describe('EditorComponent', () => { const [, noteThread] = capture(mockedSFProjectService.createNoteThread).last(); expect(noteThread.verseRef).toEqual(fromVerseRef(new VerseRef('LUK 1:1'))); expect(noteThread.notes[0].content).toEqual(XmlUtils.encodeForXml(content)); + // Close the bottom sheet, which is showing again for the still selected verse + env.clickSegmentRef('verse_1_1'); + env.wait(); env.dispose(); })); diff --git a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts index c7779cda5e..bd076abad6 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts @@ -1308,6 +1308,10 @@ export class EditorComponent extends DataLoadingComponent implements OnDestroy, this.addingMobileNote = false; this.bottomSheetRef?.dismiss(); this.toggleNoteThreadVerseRefs$.next(); + // The verse stays selected after saving, so restore the UI for adding another note to it: for users with + // edit rights, re-show the FAB; for commenters on mobile viewports (who have no FAB), reopen the bottom + // sheet with its add comment button + this.showAddCommentButton = true; } onViewerClicked(viewer: MultiCursorViewer): void { @@ -2337,7 +2341,9 @@ export class EditorComponent extends DataLoadingComponent implements OnDestroy, if (this.insertNoteFab == null || this.target?.editor == null || this.addingMobileNote) return; // getSelection can steal the focus, so we should not call this if the add mobile note bottom sheet is open const selection: Range | null | undefined = this.target.editor.getSelection(); - if (selection != null) { + // quill has no selection when the editor is not focused (e.g. after the add note bottom sheet or dialog + // closes), but while a verse is still selected the FAB should stay available and anchored to it + if (selection != null || this.commenterSelectedVerseRef != null) { this.insertNoteFab.nativeElement.style.top = `${this.target.selectionBoundsTop}px`; this.insertNoteFab.nativeElement.style.marginTop = `-${this.target.scrollPosition}px`; } else {