Add recent searches in web UI (#26834)

lolsob-rspec
Eugen Rochko 2023-09-07 14:56:19 +02:00 committed by GitHub
parent 2ac7f10374
commit 0f8203badd
6 changed files with 62 additions and 22 deletions

View File

@ -1,3 +1,7 @@
import { fromJS } from 'immutable';
import { searchHistory } from 'mastodon/settings';
import api from '../api'; import api from '../api';
import { fetchRelationships } from './accounts'; import { fetchRelationships } from './accounts';
@ -15,8 +19,7 @@ export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS'; export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL';
export const SEARCH_RESULT_CLICK = 'SEARCH_RESULT_CLICK'; export const SEARCH_HISTORY_UPDATE = 'SEARCH_HISTORY_UPDATE';
export const SEARCH_RESULT_FORGET = 'SEARCH_RESULT_FORGET';
export function changeSearch(value) { export function changeSearch(value) {
return { return {
@ -170,16 +173,34 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => {
}); });
}; };
export const clickSearchResult = (q, type) => ({ export const clickSearchResult = (q, type) => (dispatch, getState) => {
type: SEARCH_RESULT_CLICK, const previous = getState().getIn(['search', 'recent']);
const me = getState().getIn(['meta', 'me']);
const current = previous.add(fromJS({ type, q })).takeLast(4);
result: { searchHistory.set(me, current.toJS());
type, dispatch(updateSearchHistory(current));
q, };
},
export const forgetSearchResult = q => (dispatch, getState) => {
const previous = getState().getIn(['search', 'recent']);
const me = getState().getIn(['meta', 'me']);
const current = previous.filterNot(result => result.get('q') === q);
searchHistory.set(me, current.toJS());
dispatch(updateSearchHistory(current));
};
export const updateSearchHistory = recent => ({
type: SEARCH_HISTORY_UPDATE,
recent,
}); });
export const forgetSearchResult = q => ({ export const hydrateSearch = () => (dispatch, getState) => {
type: SEARCH_RESULT_FORGET, const me = getState().getIn(['meta', 'me']);
q, const history = searchHistory.get(me);
});
if (history !== null) {
dispatch(updateSearchHistory(history));
}
};

View File

@ -2,6 +2,7 @@ import { Iterable, fromJS } from 'immutable';
import { hydrateCompose } from './compose'; import { hydrateCompose } from './compose';
import { importFetchedAccounts } from './importer'; import { importFetchedAccounts } from './importer';
import { hydrateSearch } from './search';
export const STORE_HYDRATE = 'STORE_HYDRATE'; export const STORE_HYDRATE = 'STORE_HYDRATE';
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
@ -20,6 +21,7 @@ export function hydrateStore(rawState) {
}); });
dispatch(hydrateCompose()); dispatch(hydrateCompose());
dispatch(hydrateSearch());
dispatch(importFetchedAccounts(Object.values(rawState.accounts))); dispatch(importFetchedAccounts(Object.values(rawState.accounts)));
}; };
} }

View File

@ -16,6 +16,17 @@ const messages = defineMessages({
placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' }, placeholderSignedIn: { id: 'search.search_or_paste', defaultMessage: 'Search or paste URL' },
}); });
const labelForRecentSearch = search => {
switch(search.get('type')) {
case 'account':
return `@${search.get('q')}`;
case 'hashtag':
return `#${search.get('q')}`;
default:
return search.get('q');
}
};
class Search extends PureComponent { class Search extends PureComponent {
static contextTypes = { static contextTypes = {
@ -187,12 +198,16 @@ class Search extends PureComponent {
}; };
handleRecentSearchClick = search => { handleRecentSearchClick = search => {
const { onChange } = this.props;
const { router } = this.context; const { router } = this.context;
if (search.get('type') === 'account') { if (search.get('type') === 'account') {
router.history.push(`/@${search.get('q')}`); router.history.push(`/@${search.get('q')}`);
} else if (search.get('type') === 'hashtag') { } else if (search.get('type') === 'hashtag') {
router.history.push(`/tags/${search.get('q')}`); router.history.push(`/tags/${search.get('q')}`);
} else {
onChange(search.get('q'));
this._submit(search.get('type'));
} }
this._unfocus(); this._unfocus();
@ -221,11 +236,15 @@ class Search extends PureComponent {
} }
_submit (type) { _submit (type) {
const { onSubmit, openInRoute } = this.props; const { onSubmit, openInRoute, value, onClickSearchResult } = this.props;
const { router } = this.context; const { router } = this.context;
onSubmit(type); onSubmit(type);
if (value) {
onClickSearchResult(value, type);
}
if (openInRoute) { if (openInRoute) {
router.history.push('/search'); router.history.push('/search');
} }
@ -243,7 +262,7 @@ class Search extends PureComponent {
const { recent } = this.props; const { recent } = this.props;
return recent.toArray().map(search => ({ return recent.toArray().map(search => ({
label: search.get('type') === 'account' ? `@${search.get('q')}` : `#${search.get('q')}`, label: labelForRecentSearch(search),
action: () => this.handleRecentSearchClick(search), action: () => this.handleRecentSearchClick(search),
@ -359,7 +378,7 @@ class Search extends PureComponent {
{searchEnabled ? ( {searchEnabled ? (
<div className='search__popout__menu'> <div className='search__popout__menu'>
{this.defaultOptions.map(({ key, label, action }, i) => ( {this.defaultOptions.map(({ key, label, action }, i) => (
<button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === (options.length + i) })}> <button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === ((options.length || recent.size) + i) })}>
{label} {label}
</button> </button>
))} ))}

View File

@ -15,7 +15,7 @@ import Search from '../components/search';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
value: state.getIn(['search', 'value']), value: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted']), submitted: state.getIn(['search', 'submitted']),
recent: state.getIn(['search', 'recent']), recent: state.getIn(['search', 'recent']).reverse(),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -14,8 +14,7 @@ import {
SEARCH_SHOW, SEARCH_SHOW,
SEARCH_EXPAND_REQUEST, SEARCH_EXPAND_REQUEST,
SEARCH_EXPAND_SUCCESS, SEARCH_EXPAND_SUCCESS,
SEARCH_RESULT_CLICK, SEARCH_HISTORY_UPDATE,
SEARCH_RESULT_FORGET,
} from '../actions/search'; } from '../actions/search';
const initialState = ImmutableMap({ const initialState = ImmutableMap({
@ -73,10 +72,8 @@ export default function search(state = initialState, action) {
case SEARCH_EXPAND_SUCCESS: case SEARCH_EXPAND_SUCCESS:
const results = action.searchType === 'hashtags' ? ImmutableOrderedSet(fromJS(action.results.hashtags)) : action.results[action.searchType].map(item => item.id); const results = action.searchType === 'hashtags' ? ImmutableOrderedSet(fromJS(action.results.hashtags)) : action.results[action.searchType].map(item => item.id);
return state.updateIn(['results', action.searchType], list => list.union(results)); return state.updateIn(['results', action.searchType], list => list.union(results));
case SEARCH_RESULT_CLICK: case SEARCH_HISTORY_UPDATE:
return state.update('recent', set => set.add(fromJS(action.result))); return state.set('recent', ImmutableOrderedSet(fromJS(action.recent)));
case SEARCH_RESULT_FORGET:
return state.update('recent', set => set.filterNot(result => result.get('q') === action.q));
default: default:
return state; return state;
} }

View File

@ -46,3 +46,4 @@ export default class Settings {
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
export const tagHistory = new Settings('mastodon_tag_history'); export const tagHistory = new Settings('mastodon_tag_history');
export const bannerSettings = new Settings('mastodon_banner_settings'); export const bannerSettings = new Settings('mastodon_banner_settings');
export const searchHistory = new Settings('mastodon_search_history');