Add recent searches in web UI (#26834)
This commit is contained in:
		
							parent
							
								
									a90b0056cc
								
							
						
					
					
						commit
						9b2bc3d1de
					
				
					 6 changed files with 62 additions and 22 deletions
				
			
		| 
						 | 
				
			
			@ -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));
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -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)));
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 ? (
 | 
			
		||||
            <div className='search__popout__menu'>
 | 
			
		||||
              {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}
 | 
			
		||||
                </button>
 | 
			
		||||
              ))}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 => ({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
		Loading…
	
		Reference in a new issue