diff --git a/src/components/navbar/DefaultNavBar.tsx b/src/components/navbar/DefaultNavBar.tsx index 2fc4fe89..570e49db 100644 --- a/src/components/navbar/DefaultNavBar.tsx +++ b/src/components/navbar/DefaultNavBar.tsx @@ -24,14 +24,14 @@ import GetAppIcon from '@mui/icons-material/GetApp'; import GetAppOutlinedIcon from '@mui/icons-material/GetAppOutlined'; import SettingsIcon from '@mui/icons-material/Settings'; import ArrowBack from '@mui/icons-material/ArrowBack'; -import { useLocation, useNavigate } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; import { createPortal } from 'react-dom'; import { NavbarItem } from '@/typings'; import { NavBarContext } from '@/components/context/NavbarContext'; import { ExtensionOutlinedIcon } from '@/components/util/CustomExtensionOutlinedIcon'; import { DesktopSideBar } from '@/components/navbar/navigation/DesktopSideBar'; import { MobileBottomBar } from '@/components/navbar/navigation/MobileBottomBar'; -import { useHistory } from '@/util/useHistory'; +import { useBackButton } from '@/util/useBackButton.ts'; const navbarItems: Array = [ { @@ -86,12 +86,11 @@ const navbarItems: Array = [ ]; export function DefaultNavBar() { - const { title, action, override, defaultBackTo: backToUrl } = useContext(NavBarContext); + const { title, action, override } = useContext(NavBarContext); const theme = useTheme(); - const navigate = useNavigate(); - const { pathname, ...location } = useLocation(); - const history = useHistory(); + const { pathname } = useLocation(); + const handleBack = useBackButton(); const isMobileWidth = useMediaQuery(theme.breakpoints.down('sm')); const isMainRoute = navbarItems.some(({ path }) => path === pathname); @@ -108,18 +107,6 @@ export function DefaultNavBar() { navbar = it.show !== 'mobile')} />; } - const handleBack = () => { - const isLastPageInHistory = location.key === 'default'; - const wasPreviousPageReader = history[history.length - 2]?.match(/\/manga\/[0-9]+\/chapter\/[0-9]+.*/g); - - if (isLastPageInHistory || wasPreviousPageReader) { - navigate(backToUrl ?? ''); - return; - } - - navigate(-1); - }; - return ( diff --git a/src/components/util/AppbarSearch.tsx b/src/components/util/AppbarSearch.tsx index d2a3a15c..7108f42f 100644 --- a/src/components/util/AppbarSearch.tsx +++ b/src/components/util/AppbarSearch.tsx @@ -12,6 +12,7 @@ import { IconButton, Input, Tooltip } from '@mui/material'; import CancelIcon from '@mui/icons-material/Cancel'; import { useQueryParam, StringParam } from 'use-query-params'; import { useTranslation } from 'react-i18next'; +import { useBackButton } from '@/util/useBackButton.ts'; interface IProps { autoOpen?: boolean; @@ -23,22 +24,32 @@ const defaultProps = { export const AppbarSearch: React.FunctionComponent = (props) => { const { t } = useTranslation(); + const handleBack = useBackButton(); const { autoOpen } = props; const [query, setQuery] = useQueryParam('query', StringParam); const [searchOpen, setSearchOpen] = useState(!!query); const inputRef = React.useRef(); - function handleChange(e: React.ChangeEvent) { - setQuery(e.target.value === '' ? undefined : e.target.value); + const [searchString, setSearchString] = useState(query ?? ''); + + function handleChange(newQuery: string) { + if (newQuery === '') { + handleBack(); + return; + } + + setQuery(newQuery); } const cancelSearch = () => { - setQuery(null); + setSearchString(''); setSearchOpen(false); + + handleBack(); }; const handleBlur = () => { - if (!query) setSearchOpen(false); + if (!searchString) setSearchOpen(false); }; const openSearch = () => { setSearchOpen(true); @@ -48,10 +59,16 @@ export const AppbarSearch: React.FunctionComponent = (props) => { }); }; - const handleSearchShortcut = (e: KeyboardEvent) => { + const handleKeyboardEvent = (e: KeyboardEvent) => { if (e.code === 'F3' || (e.ctrlKey && e.code === 'KeyF')) { e.preventDefault(); openSearch(); + return; + } + + if (e.code === 'Enter') { + e.preventDefault(); + handleChange(searchString); } }; @@ -62,18 +79,31 @@ export const AppbarSearch: React.FunctionComponent = (props) => { }, []); useEffect(() => { - window.addEventListener('keydown', handleSearchShortcut); + if (query === undefined && searchString !== undefined) { + setSearchString(''); + setSearchOpen(false); + return; + } + + if (searchString === '' && !!query) { + setSearchString(query); + setSearchOpen(true); + } + }, [query]); + + useEffect(() => { + window.addEventListener('keydown', handleKeyboardEvent); return () => { - window.removeEventListener('keydown', handleSearchShortcut); + window.removeEventListener('keydown', handleKeyboardEvent); }; - }, [handleSearchShortcut]); + }, [handleKeyboardEvent]); if (searchOpen) { return ( setSearchString(e.target.value)} onBlur={handleBlur} inputRef={inputRef} endAdornment={ diff --git a/src/screens/SearchAll.tsx b/src/screens/SearchAll.tsx index 19bd09fd..365f0e7f 100644 --- a/src/screens/SearchAll.tsx +++ b/src/screens/SearchAll.tsx @@ -20,7 +20,7 @@ import { AppbarSearch } from '@/components/util/AppbarSearch'; import { LangSelect } from '@/components/navbar/action/LangSelect'; import { MangaGrid } from '@/components/MangaGrid'; import { useDebounce } from '@/components/manga/hooks'; -import { NavBarContext } from '@/components/context/NavbarContext.tsx'; +import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx'; type SourceLoadingState = { isLoading: boolean; hasResults: boolean; emptySearch: boolean }; type SourceToLoadingStateMap = Map; @@ -161,6 +161,8 @@ export const SearchAll: React.FC = () => { const { setTitle, setAction } = useContext(NavBarContext); + useSetDefaultBackTo('sources/all/search'); + const [query] = useQueryParam('query', StringParam); const searchString = useDebounce(query, TRIGGER_SEARCH_THRESHOLD); diff --git a/src/screens/SourceMangas.tsx b/src/screens/SourceMangas.tsx index 3bfb2439..c080d91e 100644 --- a/src/screens/SourceMangas.tsx +++ b/src/screens/SourceMangas.tsx @@ -33,7 +33,7 @@ import { GetSourceMangasFetchMutation, GetSourceMangasFetchMutationVariables, } from '@/lib/graphql/generated/graphql.ts'; -import { NavBarContext } from '@/components/context/NavbarContext.tsx'; +import { NavBarContext, useSetDefaultBackTo } from '@/components/context/NavbarContext.tsx'; const ContentTypeMenu = styled('div')(({ theme }) => ({ display: 'flex', @@ -198,21 +198,26 @@ const useSourceManga = ( export function SourceMangas() { const { t } = useTranslation(); const { setTitle, setAction } = useContext(NavBarContext); + const theme = useTheme(); const isLargeScreen = useMediaQuery(theme.breakpoints.up('sm')); const { sourceId } = useParams<{ sourceId: string }>(); const navigate = useNavigate(); + const { pathname, state: locationState } = + useLocation<{ + contentType: SourceContentType; + filtersToApply: IPos[]; + clearCache: boolean; + }>() ?? {}; const { contentType: currentLocationContentType = SourceContentType.POPULAR, filtersToApply: currentLocationFiltersToApply = [], clearCache = false, - } = useLocation<{ - contentType: SourceContentType; - filtersToApply: IPos[]; - clearCache: boolean; - }>().state ?? {}; + } = locationState ?? {}; + + useSetDefaultBackTo(pathname); const { options } = useLibraryOptionsContext(); const [query] = useQueryParam('query', StringParam); diff --git a/src/util/useBackButton.ts b/src/util/useBackButton.ts new file mode 100644 index 00000000..690942e0 --- /dev/null +++ b/src/util/useBackButton.ts @@ -0,0 +1,31 @@ +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import { useLocation, useNavigate } from 'react-router-dom'; +import { useContext } from 'react'; +import { useHistory } from '@/util/useHistory.ts'; +import { NavBarContext } from '@/components/context/NavbarContext.tsx'; + +export const useBackButton = () => { + const navigate = useNavigate(); + const history = useHistory(); + const location = useLocation(); + const { defaultBackTo: backToUrl } = useContext(NavBarContext); + + return () => { + const isLastPageInHistory = location.key === 'default'; + const wasPreviousPageReader = history[history.length - 2]?.match(/\/manga\/[0-9]+\/chapter\/[0-9]+.*/g); + + if (isLastPageInHistory || wasPreviousPageReader) { + navigate(backToUrl ?? ''); + return; + } + + navigate(-1); + }; +};