diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index c27c53df0bf..061a36bb8e7 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -356,6 +356,8 @@ const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => { cancelFetchComposeSuggestionsTags(); } + dispatch(updateSuggestionTags(token)); + api(getState).get('/api/v2/search', { cancelToken: new CancelToken(cancel => { cancelFetchComposeSuggestionsTags = cancel; diff --git a/app/javascript/mastodon/components/autosuggest_hashtag.js b/app/javascript/mastodon/components/autosuggest_hashtag.js index eabb8b1785f..e2f4e320dd1 100644 --- a/app/javascript/mastodon/components/autosuggest_hashtag.js +++ b/app/javascript/mastodon/components/autosuggest_hashtag.js @@ -9,18 +9,18 @@ export default class AutosuggestHashtag extends React.PureComponent { tag: PropTypes.shape({ name: PropTypes.string.isRequired, url: PropTypes.string, - history: PropTypes.array.isRequired, + history: PropTypes.array, }).isRequired, }; render () { const { tag } = this.props; - const weeklyUses = shortNumberFormat(tag.history.reduce((total, day) => total + (day.uses * 1), 0)); + const weeklyUses = tag.history && shortNumberFormat(tag.history.reduce((total, day) => total + (day.uses * 1), 0)); return (
#{tag.name}
-
+ {tag.history !== undefined &&
}
); } diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 7b0cdd5a564..268237846c6 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -17,6 +17,7 @@ import { COMPOSE_SUGGESTIONS_CLEAR, COMPOSE_SUGGESTIONS_READY, COMPOSE_SUGGESTION_SELECT, + COMPOSE_SUGGESTION_TAGS_UPDATE, COMPOSE_TAG_HISTORY_UPDATE, COMPOSE_SENSITIVITY_CHANGE, COMPOSE_SPOILERNESS_CHANGE, @@ -205,16 +206,36 @@ const expiresInFromExpiresAt = expires_at => { return [300, 1800, 3600, 21600, 86400, 259200, 604800].find(expires_in => expires_in >= delta) || 24 * 3600; }; -const normalizeSuggestions = (state, { accounts, emojis, tags }) => { +const mergeLocalHashtagResults = (suggestions, prefix, tagHistory) => { + prefix = prefix.toLowerCase(); + if (suggestions.length < 4) { + const localTags = tagHistory.filter(tag => tag.toLowerCase().startsWith(prefix) && !suggestions.some(suggestion => suggestion.type === 'hashtag' && suggestion.name.toLowerCase() === tag.toLowerCase())); + return suggestions.concat(localTags.slice(0, 4 - suggestions.length).toJS().map(tag => ({ type: 'hashtag', name: tag }))); + } else { + return suggestions; + } +}; + +const normalizeSuggestions = (state, { accounts, emojis, tags, token }) => { if (accounts) { return accounts.map(item => ({ id: item.id, type: 'account' })); } else if (emojis) { return emojis.map(item => ({ ...item, type: 'emoji' })); } else { - return sortHashtagsByUse(state, tags.map(item => ({ ...item, type: 'hashtag' }))); + return mergeLocalHashtagResults(sortHashtagsByUse(state, tags.map(item => ({ ...item, type: 'hashtag' }))), token.slice(1), state.get('tagHistory')); } }; +const updateSuggestionTags = (state, token) => { + const prefix = token.slice(1); + + const suggestions = state.get('suggestions').toJS(); + return state.merge({ + suggestions: ImmutableList(mergeLocalHashtagResults(suggestions, prefix, state.get('tagHistory'))), + suggestion_token: token, + }); +}; + export default function compose(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: @@ -328,6 +349,8 @@ export default function compose(state = initialState, action) { return state.set('suggestions', ImmutableList(normalizeSuggestions(state, action))).set('suggestion_token', action.token); case COMPOSE_SUGGESTION_SELECT: return insertSuggestion(state, action.position, action.token, action.completion, action.path); + case COMPOSE_SUGGESTION_TAGS_UPDATE: + return updateSuggestionTags(state, action.token); case COMPOSE_TAG_HISTORY_UPDATE: return state.set('tagHistory', fromJS(action.tags)); case TIMELINE_DELETE: