From 9b2bc3d1de301c686208b43a8efef5bc808f8e4e Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 7 Sep 2023 14:56:19 +0200 Subject: [PATCH] Add recent searches in web UI (#26834) --- app/javascript/mastodon/actions/search.js | 45 ++++++++++++++----- app/javascript/mastodon/actions/store.js | 2 + .../features/compose/components/search.jsx | 25 +++++++++-- .../compose/containers/search_container.js | 2 +- app/javascript/mastodon/reducers/search.js | 9 ++-- app/javascript/mastodon/settings.js | 1 + 6 files changed, 62 insertions(+), 22 deletions(-) diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index 21fd54076..7aea346e6 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -1,3 +1,7 @@ +import { fromJS } from 'immutable'; + +import { searchHistory } from 'mastodon/settings'; + import api from '../api'; 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_FAIL = 'SEARCH_EXPAND_FAIL'; -export const SEARCH_RESULT_CLICK = 'SEARCH_RESULT_CLICK'; -export const SEARCH_RESULT_FORGET = 'SEARCH_RESULT_FORGET'; +export const SEARCH_HISTORY_UPDATE = 'SEARCH_HISTORY_UPDATE'; export function changeSearch(value) { return { @@ -170,16 +173,34 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => { }); }; -export const clickSearchResult = (q, type) => ({ - type: SEARCH_RESULT_CLICK, +export const clickSearchResult = (q, type) => (dispatch, getState) => { + const previous = getState().getIn(['search', 'recent']); + const me = getState().getIn(['meta', 'me']); + const current = previous.add(fromJS({ type, q })).takeLast(4); - result: { - type, - q, - }, + searchHistory.set(me, current.toJS()); + dispatch(updateSearchHistory(current)); +}; + +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 => ({ - type: SEARCH_RESULT_FORGET, - q, -}); +export const hydrateSearch = () => (dispatch, getState) => { + const me = getState().getIn(['meta', 'me']); + const history = searchHistory.get(me); + + if (history !== null) { + dispatch(updateSearchHistory(history)); + } +}; \ No newline at end of file diff --git a/app/javascript/mastodon/actions/store.js b/app/javascript/mastodon/actions/store.js index 6b0743439..682b0f5db 100644 --- a/app/javascript/mastodon/actions/store.js +++ b/app/javascript/mastodon/actions/store.js @@ -2,6 +2,7 @@ import { Iterable, fromJS } from 'immutable'; import { hydrateCompose } from './compose'; import { importFetchedAccounts } from './importer'; +import { hydrateSearch } from './search'; export const STORE_HYDRATE = 'STORE_HYDRATE'; export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; @@ -20,6 +21,7 @@ export function hydrateStore(rawState) { }); dispatch(hydrateCompose()); + dispatch(hydrateSearch()); dispatch(importFetchedAccounts(Object.values(rawState.accounts))); }; } diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx index 53e1db9d4..7e1d8b760 100644 --- a/app/javascript/mastodon/features/compose/components/search.jsx +++ b/app/javascript/mastodon/features/compose/components/search.jsx @@ -16,6 +16,17 @@ const messages = defineMessages({ 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 { static contextTypes = { @@ -187,12 +198,16 @@ class Search extends PureComponent { }; handleRecentSearchClick = search => { + const { onChange } = this.props; const { router } = this.context; if (search.get('type') === 'account') { router.history.push(`/@${search.get('q')}`); } else if (search.get('type') === 'hashtag') { router.history.push(`/tags/${search.get('q')}`); + } else { + onChange(search.get('q')); + this._submit(search.get('type')); } this._unfocus(); @@ -221,11 +236,15 @@ class Search extends PureComponent { } _submit (type) { - const { onSubmit, openInRoute } = this.props; + const { onSubmit, openInRoute, value, onClickSearchResult } = this.props; const { router } = this.context; onSubmit(type); + if (value) { + onClickSearchResult(value, type); + } + if (openInRoute) { router.history.push('/search'); } @@ -243,7 +262,7 @@ class Search extends PureComponent { const { recent } = this.props; return recent.toArray().map(search => ({ - label: search.get('type') === 'account' ? `@${search.get('q')}` : `#${search.get('q')}`, + label: labelForRecentSearch(search), action: () => this.handleRecentSearchClick(search), @@ -359,7 +378,7 @@ class Search extends PureComponent { {searchEnabled ? (
{this.defaultOptions.map(({ key, label, action }, i) => ( - ))} diff --git a/app/javascript/mastodon/features/compose/containers/search_container.js b/app/javascript/mastodon/features/compose/containers/search_container.js index 299a3887e..758b6b07d 100644 --- a/app/javascript/mastodon/features/compose/containers/search_container.js +++ b/app/javascript/mastodon/features/compose/containers/search_container.js @@ -15,7 +15,7 @@ import Search from '../components/search'; const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), - recent: state.getIn(['search', 'recent']), + recent: state.getIn(['search', 'recent']).reverse(), }); const mapDispatchToProps = dispatch => ({ diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index c81d7ff3c..904e35185 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -14,8 +14,7 @@ import { SEARCH_SHOW, SEARCH_EXPAND_REQUEST, SEARCH_EXPAND_SUCCESS, - SEARCH_RESULT_CLICK, - SEARCH_RESULT_FORGET, + SEARCH_HISTORY_UPDATE, } from '../actions/search'; const initialState = ImmutableMap({ @@ -73,10 +72,8 @@ export default function search(state = initialState, action) { case SEARCH_EXPAND_SUCCESS: 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)); - case SEARCH_RESULT_CLICK: - return state.update('recent', set => set.add(fromJS(action.result))); - case SEARCH_RESULT_FORGET: - return state.update('recent', set => set.filterNot(result => result.get('q') === action.q)); + case SEARCH_HISTORY_UPDATE: + return state.set('recent', ImmutableOrderedSet(fromJS(action.recent))); default: return state; } diff --git a/app/javascript/mastodon/settings.js b/app/javascript/mastodon/settings.js index 46cfadfa3..aefb8e0e9 100644 --- a/app/javascript/mastodon/settings.js +++ b/app/javascript/mastodon/settings.js @@ -46,3 +46,4 @@ export default class Settings { export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); export const tagHistory = new Settings('mastodon_tag_history'); export const bannerSettings = new Settings('mastodon_banner_settings'); +export const searchHistory = new Settings('mastodon_search_history'); \ No newline at end of file