diff --git a/src/actions/search.ts b/src/actions/search.ts index c461cfa665..6b457a9246 100644 --- a/src/actions/search.ts +++ b/src/actions/search.ts @@ -7,7 +7,6 @@ import type { Search } from 'pl-api'; import type { SearchFilter } from 'soapbox/reducers/search'; import type { AppDispatch, RootState } from 'soapbox/store'; -const SEARCH_CHANGE = 'SEARCH_CHANGE' as const; const SEARCH_CLEAR = 'SEARCH_CLEAR' as const; const SEARCH_SHOW = 'SEARCH_SHOW' as const; const SEARCH_RESULTS_CLEAR = 'SEARCH_RESULTS_CLEAR' as const; @@ -24,23 +23,6 @@ const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL' as const; const SEARCH_ACCOUNT_SET = 'SEARCH_ACCOUNT_SET' as const; -const changeSearch = (value: string) => - (dispatch: AppDispatch) => { - // If backspaced all the way, clear the search - if (value.length === 0) { - dispatch(clearSearchResults()); - return dispatch({ - type: SEARCH_CHANGE, - value, - }); - } else { - return dispatch({ - type: SEARCH_CHANGE, - value, - }); - } - }; - const clearSearch = () => ({ type: SEARCH_CLEAR, }); @@ -49,9 +31,8 @@ const clearSearchResults = () => ({ type: SEARCH_RESULTS_CLEAR, }); -const submitSearch = (filter?: SearchFilter) => +const submitSearch = (value: string, filter?: SearchFilter) => (dispatch: AppDispatch, getState: () => RootState) => { - const value = getState().search.value; const type = filter || getState().search.filter || 'accounts'; const accountId = getState().search.accountId; @@ -103,11 +84,11 @@ const fetchSearchFail = (error: unknown) => ({ error, }); -const setFilter = (filterType: SearchFilter) => +const setFilter = (value: string, filterType: SearchFilter) => (dispatch: AppDispatch) => { - dispatch(submitSearch(filterType)); + dispatch(submitSearch(value, filterType)); - dispatch({ + return dispatch({ type: SEARCH_FILTER_SET, path: ['search', 'filter'], value: filterType, @@ -116,7 +97,7 @@ const setFilter = (filterType: SearchFilter) => const expandSearch = (type: SearchFilter) => (dispatch: AppDispatch, getState: () => RootState) => { if (type === 'links') return; - const value = getState().search.value; + const value = getState().search.submittedValue; const offset = getState().search.results[type].size; const accountId = getState().search.accountId; @@ -170,8 +151,24 @@ const setSearchAccount = (accountId: string | null) => ({ accountId, }); +type SearchAction = + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | { + type: typeof SEARCH_FILTER_SET; + path: (['search', 'filter']); + value: SearchFilter; + } + | ReturnType + | ReturnType + export { - SEARCH_CHANGE, SEARCH_CLEAR, SEARCH_SHOW, SEARCH_RESULTS_CLEAR, @@ -183,7 +180,6 @@ export { SEARCH_EXPAND_SUCCESS, SEARCH_EXPAND_FAIL, SEARCH_ACCOUNT_SET, - changeSearch, clearSearch, clearSearchResults, submitSearch, @@ -197,4 +193,5 @@ export { expandSearchFail, showSearch, setSearchAccount, + type SearchAction, }; diff --git a/src/actions/timelines.ts b/src/actions/timelines.ts index c4dc681278..1d14409f17 100644 --- a/src/actions/timelines.ts +++ b/src/actions/timelines.ts @@ -163,8 +163,10 @@ const deduplicateStatuses = (statuses: any[]) => { }; const handleTimelineExpand = (timelineId: string, fn: Promise>, isLoadingRecent: boolean, done = noOp) => - (dispatch: AppDispatch) => - fn.then(response => { + (dispatch: AppDispatch) => { + dispatch(expandTimelineRequest(timelineId)); + + return fn.then(response => { dispatch(importFetchedStatuses(response.items)); const statuses = deduplicateStatuses(response.items); @@ -183,6 +185,7 @@ const handleTimelineExpand = (timelineId: string, fn: Promise (dispatch: AppDispatch, getState: () => RootState) => { diff --git a/src/features/account-timeline/index.tsx b/src/features/account-timeline/index.tsx index 5f1bb01827..e3e31a4005 100644 --- a/src/features/account-timeline/index.tsx +++ b/src/features/account-timeline/index.tsx @@ -36,8 +36,8 @@ const AccountTimeline: React.FC = ({ params, withReplies = fal const isBlocked = useAppSelector(state => state.relationships.getIn([account?.id, 'blocked_by']) === true); const unavailable = isBlocked && !features.blockersVisible; - const isLoading = useAppSelector(state => state.getIn(['timelines', `account:${path}`, 'isLoading']) === true); - const hasMore = useAppSelector(state => state.getIn(['timelines', `account:${path}`, 'hasMore']) === true); + const isLoading = useAppSelector(state => state.timelines.get(`account:${path}`)?.isLoading === true); + const hasMore = useAppSelector(state => state.timelines.get(`account:${path}`)?.hasMore === true); const accountUsername = account?.username || params.username; diff --git a/src/features/compose/components/search-results.tsx b/src/features/compose/components/search-results.tsx index db0c4985a6..c6367081aa 100644 --- a/src/features/compose/components/search-results.tsx +++ b/src/features/compose/components/search-results.tsx @@ -47,12 +47,13 @@ const SearchResults = () => { const filterByAccount = useAppSelector((state) => state.search.accountId || undefined); const { trendingLinks } = useTrendingLinks(); const { account } = useAccount(filterByAccount); + useAppSelector((state) => console.log(state.search.toJS())); const handleLoadMore = () => dispatch(expandSearch(selectedFilter)); const handleUnsetAccount = () => dispatch(setSearchAccount(null)); - const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter)); + const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(value, newActiveFilter)); const renderFilterBar = () => { const items = []; @@ -207,6 +208,8 @@ const SearchResults = () => { } if (selectedFilter === 'links') { + loaded = true; + if (submitted) { selectFilter('accounts'); setTabKey(key => ++key); @@ -214,6 +217,8 @@ const SearchResults = () => { searchResults = ImmutableList(trendingLinks.map(trendingLink => )); } } + + console.log(submitted, loaded, selectedFilter); return ( <> diff --git a/src/features/compose/components/search.tsx b/src/features/compose/components/search.tsx index 14dcf49a0f..d1804c36a7 100644 --- a/src/features/compose/components/search.tsx +++ b/src/features/compose/components/search.tsx @@ -1,11 +1,10 @@ import clsx from 'clsx'; import debounce from 'lodash/debounce'; -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { defineMessages, useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { - changeSearch, clearSearch, clearSearchResults, setSearchAccount, @@ -41,6 +40,8 @@ interface ISearch { } const Search = (props: ISearch) => { + const submittedValue = useAppSelector((state) => state.search.submittedValue); + const [value, setValue] = useState(submittedValue); const { autoFocus = false, autoSubmit = false, @@ -52,20 +53,19 @@ const Search = (props: ISearch) => { const history = useHistory(); const intl = useIntl(); - const value = useAppSelector((state) => state.search.value); const submitted = useAppSelector((state) => state.search.submitted); - const debouncedSubmit = useCallback(debounce(() => { - dispatch(submitSearch()); + const debouncedSubmit = useCallback(debounce((value: string) => { + dispatch(submitSearch(value)); }, 900), []); const handleChange = (event: React.ChangeEvent) => { const { value } = event.target; - dispatch(changeSearch(value)); + setValue(value); if (autoSubmit) { - debouncedSubmit(); + debouncedSubmit(value); } }; @@ -80,11 +80,11 @@ const Search = (props: ISearch) => { const handleSubmit = () => { if (openInRoute) { dispatch(setSearchAccount(null)); - dispatch(submitSearch()); + dispatch(submitSearch(value)); history.push('/search'); } else { - dispatch(submitSearch()); + dispatch(submitSearch(value)); } }; @@ -129,6 +129,10 @@ const Search = (props: ISearch) => { className: 'pr-10 rtl:pl-10 rtl:pr-3', }; + useEffect(() => { + if (value !== submittedValue) setValue(submittedValue); + }, [submittedValue]); + if (autosuggest) { componentProps.onSelected = handleSelected; componentProps.menu = makeMenu(); diff --git a/src/features/ui/components/trends-panel.tsx b/src/features/ui/components/trends-panel.tsx index d50e0c14c0..945b4ae66b 100644 --- a/src/features/ui/components/trends-panel.tsx +++ b/src/features/ui/components/trends-panel.tsx @@ -27,7 +27,7 @@ const TrendsPanel = ({ limit }: ITrendsPanel) => { const { data: trends, isFetching } = useTrends(); const setHashtagsFilter = () => { - dispatch(setFilter('hashtags')); + dispatch(setFilter('', 'hashtags')); }; if (!isFetching && !trends?.length) { diff --git a/src/reducers/draft-statuses.ts b/src/reducers/draft-statuses.ts index 158eacebb8..b7c8498082 100644 --- a/src/reducers/draft-statuses.ts +++ b/src/reducers/draft-statuses.ts @@ -1,6 +1,6 @@ import { List as ImmutableList, Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord, fromJS } from 'immutable'; -import { COMPOSE_SUBMIT_SUCCESS } from 'soapbox/actions/compose'; +import { COMPOSE_SUBMIT_SUCCESS, type ComposeAction } from 'soapbox/actions/compose'; import { DRAFT_STATUSES_FETCH_SUCCESS, PERSIST_DRAFT_STATUS, CANCEL_DRAFT_STATUS } from 'soapbox/actions/draft-statuses'; import KVStore from 'soapbox/storage/kv-store'; @@ -48,7 +48,7 @@ const persistState = (state: State, accountUrl: string) => { return state; }; -const scheduled_statuses = (state: State = initialState, action: AnyAction) => { +const scheduled_statuses = (state: State = initialState, action: AnyAction | ComposeAction) => { switch (action.type) { case DRAFT_STATUSES_FETCH_SUCCESS: return importStatuses(state, action.statuses); diff --git a/src/reducers/search.ts b/src/reducers/search.ts index b668384afa..b599de7714 100644 --- a/src/reducers/search.ts +++ b/src/reducers/search.ts @@ -5,9 +5,9 @@ import { COMPOSE_REPLY, COMPOSE_DIRECT, COMPOSE_QUOTE, + type ComposeAction, } from '../actions/compose'; import { - SEARCH_CHANGE, SEARCH_CLEAR, SEARCH_FETCH_REQUEST, SEARCH_FETCH_SUCCESS, @@ -17,10 +17,10 @@ import { SEARCH_EXPAND_SUCCESS, SEARCH_ACCOUNT_SET, SEARCH_RESULTS_CLEAR, + type SearchAction, } from '../actions/search'; import type { Search, Tag } from 'pl-api'; -import type { AnyAction } from 'redux'; import type { APIEntity } from 'soapbox/types/entities'; const ResultsRecord = ImmutableRecord({ @@ -39,14 +39,12 @@ const ResultsRecord = ImmutableRecord({ }); const ReducerRecord = ImmutableRecord({ - value: '', submitted: false, submittedValue: '', hidden: false, results: ResultsRecord(), filter: 'accounts' as SearchFilter, accountId: null as string | null, - next: null as string | null, }); type State = ReturnType; @@ -55,18 +53,18 @@ type SearchFilter = 'accounts' | 'statuses' | 'groups' | 'hashtags' | 'links'; const toIds = (items: APIEntities = []) => ImmutableOrderedSet(items.map(item => item.id)); -const importResults = (state: State, results: Search, searchTerm: string, searchType: SearchFilter, next: string | null) => +const importResults = (state: State, results: Search, searchTerm: string, searchType: SearchFilter) => state.withMutations(state => { - if (state.value === searchTerm && state.filter === searchType) { + if (state.submittedValue === searchTerm && state.filter === searchType) { state.set('results', ResultsRecord({ accounts: toIds(results.accounts), statuses: toIds(results.statuses), groups: toIds(results.groups), hashtags: ImmutableOrderedSet(results.hashtags), // it's a list of records - accountsHasMore: results.accounts.length >= 20, - statusesHasMore: results.statuses.length >= 20, - groupsHasMore: results.groups?.length >= 20, - hashtagsHasMore: results.hashtags.length >= 20, + accountsHasMore: results.accounts.length !== 0, + statusesHasMore: results.statuses.length !== 0, + groupsHasMore: results.groups?.length !== 0, + hashtagsHasMore: results.hashtags.length !== 0, accountsLoaded: true, statusesLoaded: true, groupsLoaded: true, @@ -74,16 +72,14 @@ const importResults = (state: State, results: Search, searchTerm: string, search })); state.set('submitted', true); - state.set('next', next); } }); -const paginateResults = (state: State, searchType: SearchFilter, results: APIEntity, searchTerm: string, next: string | null) => +const paginateResults = (state: State, searchType: SearchFilter, results: APIEntity, searchTerm: string) => state.withMutations(state => { - if (state.value === searchTerm) { + if (state.submittedValue === searchTerm) { state.setIn(['results', `${searchType}HasMore`], results[searchType].length >= 20); state.setIn(['results', `${searchType}Loaded`], true); - state.set('next', next); state.updateIn(['results', searchType], items => { const data = results[searchType]; // Hashtags are a list of maps. Others are IDs. @@ -103,15 +99,12 @@ const handleSubmitted = (state: State, value: string) => state.set('submittedValue', value); }); -const search = (state = ReducerRecord(), action: AnyAction) => { +const search = (state = ReducerRecord(), action: SearchAction | ComposeAction) => { switch (action.type) { - case SEARCH_CHANGE: - return state.set('value', action.value); case SEARCH_CLEAR: return ReducerRecord(); case SEARCH_RESULTS_CLEAR: return state.merge({ - value: '', results: ResultsRecord(), submitted: false, submittedValue: '', @@ -126,13 +119,13 @@ const search = (state = ReducerRecord(), action: AnyAction) => { case SEARCH_FETCH_REQUEST: return handleSubmitted(state, action.value); case SEARCH_FETCH_SUCCESS: - return importResults(state, action.results, action.searchTerm, action.searchType, action.next); + return importResults(state, action.results, action.searchTerm, action.searchType); case SEARCH_FILTER_SET: return state.set('filter', action.value); case SEARCH_EXPAND_REQUEST: return state.setIn(['results', `${action.searchType}Loaded`], false); case SEARCH_EXPAND_SUCCESS: - return paginateResults(state, action.searchType, action.results, action.searchTerm, action.next); + return paginateResults(state, action.searchType, action.results, action.searchTerm); case SEARCH_ACCOUNT_SET: if (!action.accountId) return state.merge({ results: ResultsRecord(),