From 7aaa8f703e932b9f357a2e41295290f71a7839c1 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Thu, 16 Jan 2025 10:27:38 +1030 Subject: [PATCH 1/4] fix: auto-focus the search keyword input when in search modal only so that library authoring navigate events don't change element focus. --- src/library-authoring/LibraryAuthoringPage.test.tsx | 4 ++++ src/search-manager/SearchKeywordsField.tsx | 8 ++++++-- src/search-modal/SearchModal.test.tsx | 10 ++++++++++ src/search-modal/SearchModal.tsx | 6 +++++- src/search-modal/SearchUI.tsx | 11 +++++++++-- 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/library-authoring/LibraryAuthoringPage.test.tsx b/src/library-authoring/LibraryAuthoringPage.test.tsx index 2b55375fd2..ed02d68bd1 100644 --- a/src/library-authoring/LibraryAuthoringPage.test.tsx +++ b/src/library-authoring/LibraryAuthoringPage.test.tsx @@ -111,6 +111,10 @@ describe('', () => { expect(screen.getAllByText('Recently Modified').length).toEqual(1); expect((await screen.findAllByText('Introduction to Testing'))[0]).toBeInTheDocument(); + // Search box should not have focus on page load + const searchBox = screen.getByRole('searchbox'); + expect(searchBox).not.toHaveFocus(); + // Navigate to the components tab fireEvent.click(screen.getByRole('tab', { name: 'Components' })); // "Recently Modified" default sort shown diff --git a/src/search-manager/SearchKeywordsField.tsx b/src/search-manager/SearchKeywordsField.tsx index 14a6a06dc9..90c09fdd93 100644 --- a/src/search-manager/SearchKeywordsField.tsx +++ b/src/search-manager/SearchKeywordsField.tsx @@ -7,7 +7,11 @@ import { useSearchContext } from './SearchManager'; /** * The "main" input field where users type in search keywords. The search happens as they type (no need to press enter). */ -const SearchKeywordsField: React.FC<{ className?: string, placeholder?: string }> = (props) => { +const SearchKeywordsField: React.FC<{ + className?: string, + placeholder?: string, + autoFocus?: boolean, +}> = (props) => { const intl = useIntl(); const { searchKeywords, setSearchKeywords, usageKey } = useSearchContext(); const defaultPlaceholder = usageKey ? messages.clearUsageKeyToSearch : messages.inputPlaceholder; @@ -24,7 +28,7 @@ const SearchKeywordsField: React.FC<{ className?: string, placeholder?: string } > diff --git a/src/search-modal/SearchModal.test.tsx b/src/search-modal/SearchModal.test.tsx index ef35726395..9449a9efdc 100644 --- a/src/search-modal/SearchModal.test.tsx +++ b/src/search-modal/SearchModal.test.tsx @@ -73,4 +73,14 @@ describe('', () => { const { findByText } = render(); expect(await findByText('An error occurred. Unable to load search results.')).toBeInTheDocument(); }); + + it('should set focus on the search input box when loaded in the modal', async () => { + axiosMock.onGet(getContentSearchConfigUrl()).replyOnce(200, { + url: 'https://meilisearch.example.com', + index: 'test-index', + apiKey: 'test-api-key', + }); + const { getByRole } = render(); + expect(getByRole('searchbox')).toHaveFocus(); + }); }); diff --git a/src/search-modal/SearchModal.tsx b/src/search-modal/SearchModal.tsx index 2e552fb6e8..197ba6c708 100644 --- a/src/search-modal/SearchModal.tsx +++ b/src/search-modal/SearchModal.tsx @@ -21,7 +21,11 @@ const SearchModal: React.FC<{ courseId?: string, isOpen: boolean, onClose: () => isFullscreenOnMobile className="courseware-search-modal" > - + ); }; diff --git a/src/search-modal/SearchUI.tsx b/src/search-modal/SearchUI.tsx index 2df074111c..a70e1f69fe 100644 --- a/src/search-modal/SearchUI.tsx +++ b/src/search-modal/SearchUI.tsx @@ -19,7 +19,11 @@ import EmptyStates from './EmptyStates'; import SearchResults from './SearchResults'; import messages from './messages'; -const SearchUI: React.FC<{ courseId?: string, closeSearchModal?: () => void }> = (props) => { +const SearchUI: React.FC<{ + courseId?: string, + autoFocus?: boolean, + closeSearchModal?: () => void, +}> = (props) => { const hasCourseId = Boolean(props.courseId); const [searchThisCourseEnabled, setSearchThisCourse] = React.useState(hasCourseId); const switchToThisCourse = React.useCallback(() => setSearchThisCourse(true), []); @@ -39,7 +43,10 @@ const SearchUI: React.FC<{ courseId?: string, closeSearchModal?: () => void }> =
- + Date: Thu, 16 Jan 2025 11:52:08 -0300 Subject: [PATCH 2/4] fix: prevent LibraryLayout remount --- src/library-authoring/LibraryLayout.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/library-authoring/LibraryLayout.tsx b/src/library-authoring/LibraryLayout.tsx index c093af7ad3..728d31cabb 100644 --- a/src/library-authoring/LibraryLayout.tsx +++ b/src/library-authoring/LibraryLayout.tsx @@ -3,7 +3,6 @@ import { Route, Routes, useParams, - useLocation, } from 'react-router-dom'; import { ROUTES } from './routes'; @@ -23,12 +22,8 @@ const LibraryLayout = () => { throw new Error('Error: route is missing libraryId.'); } - const location = useLocation(); const context = useCallback((childPage) => ( { - ), [location.pathname]); + ), []); return ( From 244cfc6a0cf303aa8b1901015af95de084131160 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Fri, 17 Jan 2025 11:01:30 +1030 Subject: [PATCH 3/4] fix: set default react-query staleTime to 1h. --- src/index.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/index.jsx b/src/index.jsx index 34f27f1b9a..c885dac167 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -35,7 +35,13 @@ import { ToastProvider } from './generic/toast-context'; import 'react-datepicker/dist/react-datepicker.css'; import './index.scss'; -const queryClient = new QueryClient(); +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 60_000, // If cache is up to one hour old, no need to re-fetch + }, + }, +}); const App = () => { useEffect(() => { From 693da9cd5ac5f6c9f4773a42648ca01b0db7e4f7 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Fri, 17 Jan 2025 17:30:12 +1030 Subject: [PATCH 4/4] fix: remount LibraryLayout on collectionId change --- src/library-authoring/LibraryLayout.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/library-authoring/LibraryLayout.tsx b/src/library-authoring/LibraryLayout.tsx index 728d31cabb..add33f95a0 100644 --- a/src/library-authoring/LibraryLayout.tsx +++ b/src/library-authoring/LibraryLayout.tsx @@ -2,10 +2,12 @@ import { useCallback } from 'react'; import { Route, Routes, + useMatch, useParams, + type PathMatch, } from 'react-router-dom'; -import { ROUTES } from './routes'; +import { BASE_ROUTE, ROUTES } from './routes'; import LibraryAuthoringPage from './LibraryAuthoringPage'; import { LibraryProvider } from './common/context/LibraryContext'; import { SidebarProvider } from './common/context/SidebarContext'; @@ -22,8 +24,15 @@ const LibraryLayout = () => { throw new Error('Error: route is missing libraryId.'); } + // The top-level route is `${BASE_ROUTE}/*`, so match will always be non-null. + const match = useMatch(`${BASE_ROUTE}${ROUTES.COLLECTION}`) as PathMatch<'libraryId' | 'collectionId'> | null; + const collectionId = match?.params.collectionId; + const context = useCallback((childPage) => ( { - ), []); + ), [collectionId]); return (