Merge branch 'search-from-account' into 'develop'
Allow to search for posts from given account See merge request soapbox-pub/soapbox-fe!1710
This commit is contained in:
commit
a2f9c7d97b
|
@ -22,6 +22,8 @@ const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
|
|||
const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
|
||||
const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL';
|
||||
|
||||
const SEARCH_ACCOUNT_SET = 'SEARCH_ACCOUNT_SET';
|
||||
|
||||
const changeSearch = (value: string) =>
|
||||
(dispatch: AppDispatch) => {
|
||||
// If backspaced all the way, clear the search
|
||||
|
@ -43,6 +45,7 @@ const submitSearch = (filter?: SearchFilter) =>
|
|||
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||
const value = getState().search.value;
|
||||
const type = filter || getState().search.filter || 'accounts';
|
||||
const accountId = getState().search.accountId;
|
||||
|
||||
// An empty search doesn't return any results
|
||||
if (value.length === 0) {
|
||||
|
@ -51,13 +54,17 @@ const submitSearch = (filter?: SearchFilter) =>
|
|||
|
||||
dispatch(fetchSearchRequest(value));
|
||||
|
||||
const params: Record<string, any> = {
|
||||
q: value,
|
||||
resolve: true,
|
||||
limit: 20,
|
||||
type,
|
||||
};
|
||||
|
||||
if (accountId) params.account_id = accountId;
|
||||
|
||||
api(getState).get('/api/v2/search', {
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
limit: 20,
|
||||
type,
|
||||
},
|
||||
params,
|
||||
}).then(response => {
|
||||
if (response.data.accounts) {
|
||||
dispatch(importFetchedAccounts(response.data.accounts));
|
||||
|
@ -151,6 +158,11 @@ const showSearch = () => ({
|
|||
type: SEARCH_SHOW,
|
||||
});
|
||||
|
||||
const setSearchAccount = (accountId: string) => ({
|
||||
type: SEARCH_ACCOUNT_SET,
|
||||
accountId,
|
||||
});
|
||||
|
||||
export {
|
||||
SEARCH_CHANGE,
|
||||
SEARCH_CLEAR,
|
||||
|
@ -162,6 +174,7 @@ export {
|
|||
SEARCH_EXPAND_REQUEST,
|
||||
SEARCH_EXPAND_SUCCESS,
|
||||
SEARCH_EXPAND_FAIL,
|
||||
SEARCH_ACCOUNT_SET,
|
||||
changeSearch,
|
||||
clearSearch,
|
||||
submitSearch,
|
||||
|
@ -174,4 +187,5 @@ export {
|
|||
expandSearchSuccess,
|
||||
expandSearchFail,
|
||||
showSearch,
|
||||
setSearchAccount,
|
||||
};
|
||||
|
|
|
@ -64,6 +64,7 @@ const messages = defineMessages({
|
|||
demoteToUser: { id: 'admin.users.actions.demote_to_user', defaultMessage: 'Demote @{name} to a regular user' },
|
||||
suggestUser: { id: 'admin.users.actions.suggest_user', defaultMessage: 'Suggest @{name}' },
|
||||
unsuggestUser: { id: 'admin.users.actions.unsuggest_user', defaultMessage: 'Unsuggest @{name}' },
|
||||
search: { id: 'account.search', defaultMessage: 'Search from @{name}' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => {
|
||||
|
@ -274,6 +275,14 @@ class Header extends ImmutablePureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
if (features.searchFromAccount) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.search, { name: account.get('username') }),
|
||||
action: this.props.onSearch,
|
||||
icon: require('@tabler/icons/search.svg'),
|
||||
});
|
||||
}
|
||||
|
||||
if (features.removeFromFollowers && account.relationship?.followed_by) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.removeFromFollowers),
|
||||
|
|
|
@ -26,6 +26,7 @@ class Header extends ImmutablePureComponent {
|
|||
onEndorseToggle: PropTypes.func.isRequired,
|
||||
onAddToList: PropTypes.func.isRequired,
|
||||
onRemoveFromFollowers: PropTypes.func.isRequired,
|
||||
onSearch: PropTypes.func.isRequired,
|
||||
username: PropTypes.string,
|
||||
history: PropTypes.object,
|
||||
};
|
||||
|
@ -146,6 +147,10 @@ class Header extends ImmutablePureComponent {
|
|||
this.props.onRemoveFromFollowers(this.props.account);
|
||||
}
|
||||
|
||||
handleSearch = () => {
|
||||
this.props.onSearch(this.props.account, this.props.history);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account } = this.props;
|
||||
const moved = (account) ? account.get('moved') : false;
|
||||
|
@ -183,6 +188,7 @@ class Header extends ImmutablePureComponent {
|
|||
onUnsuggestUser={this.handleUnsuggestUser}
|
||||
onShowNote={this.handleShowNote}
|
||||
onRemoveFromFollowers={this.handleRemoveFromFollowers}
|
||||
onSearch={this.handleSearch}
|
||||
username={this.props.username}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -36,6 +36,7 @@ import { openModal } from 'soapbox/actions/modals';
|
|||
import { deactivateUserModal, deleteUserModal } from 'soapbox/actions/moderation';
|
||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||
import { initReport } from 'soapbox/actions/reports';
|
||||
import { setSearchAccount } from 'soapbox/actions/search';
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
@ -291,6 +292,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
onSearch(account, router) {
|
||||
dispatch((dispatch) => {
|
||||
dispatch(setSearchAccount(account.id));
|
||||
router.push('/search');
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
||||
|
|
|
@ -2,11 +2,12 @@ import classNames from 'classnames';
|
|||
import React, { useEffect, useRef } from 'react';
|
||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { expandSearch, setFilter } from 'soapbox/actions/search';
|
||||
import { clearSearch, expandSearch, setFilter } from 'soapbox/actions/search';
|
||||
import { fetchTrendingStatuses } from 'soapbox/actions/trending_statuses';
|
||||
import Hashtag from 'soapbox/components/hashtag';
|
||||
import IconButton from 'soapbox/components/icon_button';
|
||||
import ScrollableList from 'soapbox/components/scrollable_list';
|
||||
import { Tabs } from 'soapbox/components/ui';
|
||||
import { HStack, Tabs, Text } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account_container';
|
||||
import StatusContainer from 'soapbox/containers/status_container';
|
||||
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account';
|
||||
|
@ -37,9 +38,13 @@ const SearchResults = () => {
|
|||
const trends = useAppSelector((state) => state.trends.items);
|
||||
const submitted = useAppSelector((state) => state.search.submitted);
|
||||
const selectedFilter = useAppSelector((state) => state.search.filter);
|
||||
const filterByAccount = useAppSelector((state) => state.search.accountId);
|
||||
const account = useAppSelector((state) => state.accounts.get(filterByAccount)?.acct);
|
||||
|
||||
const handleLoadMore = () => dispatch(expandSearch(selectedFilter));
|
||||
|
||||
const handleClearSearch = () => dispatch(clearSearch());
|
||||
|
||||
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
|
||||
|
||||
const renderFilterBar = () => {
|
||||
|
@ -189,7 +194,18 @@ const SearchResults = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
{renderFilterBar()}
|
||||
{filterByAccount ? (
|
||||
<HStack className='mb-4 pb-4 px-2 border-solid border-b border-gray-200 dark:border-gray-800' space={2}>
|
||||
<IconButton iconClassName='h-5 w-5' src={require('@tabler/icons/x.svg')} onClick={handleClearSearch} />
|
||||
<Text>
|
||||
<FormattedMessage
|
||||
id='search_results.filter_message'
|
||||
defaultMessage='You are searching for posts from @{acct}.'
|
||||
values={{ acct: account }}
|
||||
/>
|
||||
</Text>
|
||||
</HStack>
|
||||
) : renderFilterBar()}
|
||||
|
||||
{noResultsMessage || (
|
||||
<ScrollableList
|
||||
|
|
|
@ -27,6 +27,7 @@ describe('search reducer', () => {
|
|||
hashtagsLoaded: false,
|
||||
},
|
||||
filter: 'accounts',
|
||||
accountId: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -68,6 +69,7 @@ describe('search reducer', () => {
|
|||
hashtagsLoaded: false,
|
||||
},
|
||||
filter: 'accounts',
|
||||
accountId: null,
|
||||
};
|
||||
|
||||
expect(reducer(state, action).toJS()).toEqual(expected);
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
SEARCH_FILTER_SET,
|
||||
SEARCH_EXPAND_REQUEST,
|
||||
SEARCH_EXPAND_SUCCESS,
|
||||
SEARCH_ACCOUNT_SET,
|
||||
} from '../actions/search';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
|
@ -41,6 +42,7 @@ const ReducerRecord = ImmutableRecord({
|
|||
hidden: false,
|
||||
results: ResultsRecord(),
|
||||
filter: 'accounts' as SearchFilter,
|
||||
accountId: null as string | null,
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
@ -120,6 +122,8 @@ export default function search(state = ReducerRecord(), action: AnyAction) {
|
|||
return state.setIn(['results', `${action.searchType}Loaded`], false);
|
||||
case SEARCH_EXPAND_SUCCESS:
|
||||
return paginateResults(state, action.searchType, action.results, action.searchTerm);
|
||||
case SEARCH_ACCOUNT_SET:
|
||||
return ReducerRecord({ accountId: action.accountId, filter: 'statuses' });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -536,6 +536,16 @@ const getInstanceFeatures = (instance: Instance) => {
|
|||
*/
|
||||
scopes: v.software === PLEROMA ? 'read write follow push admin' : 'read write follow push',
|
||||
|
||||
/**
|
||||
* Ability to search statuses from the given account.
|
||||
* @see {@link https://docs.joinmastodon.org/methods/search/}
|
||||
* @see POST /api/v2/search
|
||||
*/
|
||||
searchFromAccount: any([
|
||||
v.software === MASTODON && gte(v.version, '2.8.0'),
|
||||
v.software === PLEROMA && gte(v.version, '1.0.0'),
|
||||
]),
|
||||
|
||||
/**
|
||||
* Ability to manage account security settings.
|
||||
* @see POST /api/pleroma/change_password
|
||||
|
|
Loading…
Reference in New Issue