This is a functionality similar to one implemented in Pawoo:
21a3c70f80
master
@@ -1,6 +1,7 @@ | |||
import api from '../api'; | |||
import { throttle } from 'lodash'; | |||
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; | |||
import { tagHistory } from '../settings'; | |||
import { useEmoji } from './emojis'; | |||
import { | |||
@@ -27,6 +28,9 @@ export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO'; | |||
export const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR'; | |||
export const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY'; | |||
export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT'; | |||
export const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE'; | |||
export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE'; | |||
export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; | |||
export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; | |||
@@ -111,6 +115,7 @@ export function submitCompose() { | |||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), | |||
}, | |||
}).then(function (response) { | |||
dispatch(insertIntoTagHistory(response.data.tags)); | |||
dispatch(submitComposeSuccess({ ...response.data })); | |||
// To make the app more responsive, immediately get the status into the columns | |||
@@ -273,12 +278,22 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => { | |||
dispatch(readyComposeSuggestionsEmojis(token, results)); | |||
}; | |||
const fetchComposeSuggestionsTags = (dispatch, getState, token) => { | |||
dispatch(updateSuggestionTags(token)); | |||
}; | |||
export function fetchComposeSuggestions(token) { | |||
return (dispatch, getState) => { | |||
if (token[0] === ':') { | |||
switch (token[0]) { | |||
case ':': | |||
fetchComposeSuggestionsEmojis(dispatch, getState, token); | |||
} else { | |||
break; | |||
case '#': | |||
fetchComposeSuggestionsTags(dispatch, getState, token); | |||
break; | |||
default: | |||
fetchComposeSuggestionsAccounts(dispatch, getState, token); | |||
break; | |||
} | |||
}; | |||
}; | |||
@@ -308,6 +323,9 @@ export function selectComposeSuggestion(position, token, suggestion) { | |||
startPosition = position - 1; | |||
dispatch(useEmoji(suggestion)); | |||
} else if (suggestion[0] === '#') { | |||
completion = suggestion; | |||
startPosition = position - 1; | |||
} else { | |||
completion = getState().getIn(['accounts', suggestion, 'acct']); | |||
startPosition = position; | |||
@@ -322,6 +340,48 @@ export function selectComposeSuggestion(position, token, suggestion) { | |||
}; | |||
}; | |||
export function updateSuggestionTags(token) { | |||
return { | |||
type: COMPOSE_SUGGESTION_TAGS_UPDATE, | |||
token, | |||
}; | |||
} | |||
export function updateTagHistory(tags) { | |||
return { | |||
type: COMPOSE_TAG_HISTORY_UPDATE, | |||
tags, | |||
}; | |||
} | |||
export function hydrateCompose() { | |||
return (dispatch, getState) => { | |||
const me = getState().getIn(['meta', 'me']); | |||
const history = tagHistory.get(me); | |||
if (history !== null) { | |||
dispatch(updateTagHistory(history)); | |||
} | |||
}; | |||
} | |||
function insertIntoTagHistory(tags) { | |||
return (dispatch, getState) => { | |||
const state = getState(); | |||
const oldHistory = state.getIn(['compose', 'tagHistory']); | |||
const me = state.getIn(['meta', 'me']); | |||
const names = tags.map(({ name }) => name); | |||
const intersectedOldHistory = oldHistory.filter(name => !names.includes(name)); | |||
names.push(...intersectedOldHistory.toJS()); | |||
const newHistory = names.slice(0, 1000); | |||
tagHistory.set(me, newHistory); | |||
dispatch(updateTagHistory(newHistory)); | |||
}; | |||
} | |||
export function mountCompose() { | |||
return { | |||
type: COMPOSE_MOUNT, | |||
@@ -1,4 +1,5 @@ | |||
import { Iterable, fromJS } from 'immutable'; | |||
import { hydrateCompose } from './compose'; | |||
export const STORE_HYDRATE = 'STORE_HYDRATE'; | |||
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; | |||
@@ -8,10 +9,14 @@ const convertState = rawState => | |||
Iterable.isIndexed(v) ? v.toList() : v.toMap()); | |||
export function hydrateStore(rawState) { | |||
const state = convertState(rawState); | |||
return dispatch => { | |||
const state = convertState(rawState); | |||
return { | |||
type: STORE_HYDRATE, | |||
state, | |||
dispatch({ | |||
type: STORE_HYDRATE, | |||
state, | |||
}); | |||
dispatch(hydrateCompose()); | |||
}; | |||
}; |
@@ -20,7 +20,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => { | |||
word = str.slice(left, right + caretPosition); | |||
} | |||
if (!word || word.trim().length < 3 || ['@', ':'].indexOf(word[0]) === -1) { | |||
if (!word || word.trim().length < 3 || ['@', ':', '#'].indexOf(word[0]) === -1) { | |||
return [null, null]; | |||
} | |||
@@ -170,6 +170,9 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { | |||
if (typeof suggestion === 'object') { | |||
inner = <AutosuggestEmoji emoji={suggestion} />; | |||
key = suggestion.id; | |||
} else if (suggestion[0] === '#') { | |||
inner = suggestion; | |||
key = suggestion; | |||
} else { | |||
inner = <AutosuggestAccountContainer id={suggestion} />; | |||
key = suggestion; | |||
@@ -16,6 +16,8 @@ import { | |||
COMPOSE_SUGGESTIONS_CLEAR, | |||
COMPOSE_SUGGESTIONS_READY, | |||
COMPOSE_SUGGESTION_SELECT, | |||
COMPOSE_SUGGESTION_TAGS_UPDATE, | |||
COMPOSE_TAG_HISTORY_UPDATE, | |||
COMPOSE_SENSITIVITY_CHANGE, | |||
COMPOSE_SPOILERNESS_CHANGE, | |||
COMPOSE_SPOILER_TEXT_CHANGE, | |||
@@ -54,6 +56,7 @@ const initialState = ImmutableMap({ | |||
default_sensitive: false, | |||
resetFileKey: Math.floor((Math.random() * 0x10000)), | |||
idempotencyKey: null, | |||
tagHistory: ImmutableList(), | |||
}); | |||
function statusToTextMentions(state, status) { | |||
@@ -122,6 +125,18 @@ const insertSuggestion = (state, position, token, completion) => { | |||
}); | |||
}; | |||
const updateSuggestionTags = (state, token) => { | |||
const prefix = token.slice(1); | |||
return state.merge({ | |||
suggestions: state.get('tagHistory') | |||
.filter(tag => tag.startsWith(prefix)) | |||
.slice(0, 4) | |||
.map(tag => '#' + tag), | |||
suggestion_token: token, | |||
}); | |||
}; | |||
const insertEmoji = (state, position, emojiData) => { | |||
const emoji = emojiData.native; | |||
@@ -252,6 +267,10 @@ export default function compose(state = initialState, action) { | |||
return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).set('suggestion_token', action.token); | |||
case COMPOSE_SUGGESTION_SELECT: | |||
return insertSuggestion(state, action.position, action.token, action.completion); | |||
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: | |||
if (action.id === state.get('in_reply_to')) { | |||
return state.set('in_reply_to', null); | |||
@@ -44,3 +44,4 @@ export default class Settings { | |||
} | |||
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data'); | |||
export const tagHistory = new Settings('mastodon_tag_history'); |