1 import api
from '../api';
2 import { CancelToken
} from 'axios';
3 import { throttle
} from 'lodash';
4 import { search as emojiSearch
} from '../features/emoji/emoji_mart_search_light';
5 import { tagHistory
} from '../settings';
6 import { useEmoji
} from './emojis';
11 refreshCommunityTimeline
,
12 refreshPublicTimeline
,
15 let cancelFetchComposeSuggestionsAccounts
;
17 export const COMPOSE_CHANGE
= 'COMPOSE_CHANGE';
18 export const COMPOSE_SUBMIT_REQUEST
= 'COMPOSE_SUBMIT_REQUEST';
19 export const COMPOSE_SUBMIT_SUCCESS
= 'COMPOSE_SUBMIT_SUCCESS';
20 export const COMPOSE_SUBMIT_FAIL
= 'COMPOSE_SUBMIT_FAIL';
21 export const COMPOSE_REPLY
= 'COMPOSE_REPLY';
22 export const COMPOSE_REPLY_CANCEL
= 'COMPOSE_REPLY_CANCEL';
23 export const COMPOSE_MENTION
= 'COMPOSE_MENTION';
24 export const COMPOSE_RESET
= 'COMPOSE_RESET';
25 export const COMPOSE_UPLOAD_REQUEST
= 'COMPOSE_UPLOAD_REQUEST';
26 export const COMPOSE_UPLOAD_SUCCESS
= 'COMPOSE_UPLOAD_SUCCESS';
27 export const COMPOSE_UPLOAD_FAIL
= 'COMPOSE_UPLOAD_FAIL';
28 export const COMPOSE_UPLOAD_PROGRESS
= 'COMPOSE_UPLOAD_PROGRESS';
29 export const COMPOSE_UPLOAD_UNDO
= 'COMPOSE_UPLOAD_UNDO';
31 export const COMPOSE_SUGGESTIONS_CLEAR
= 'COMPOSE_SUGGESTIONS_CLEAR';
32 export const COMPOSE_SUGGESTIONS_READY
= 'COMPOSE_SUGGESTIONS_READY';
33 export const COMPOSE_SUGGESTION_SELECT
= 'COMPOSE_SUGGESTION_SELECT';
34 export const COMPOSE_SUGGESTION_TAGS_UPDATE
= 'COMPOSE_SUGGESTION_TAGS_UPDATE';
36 export const COMPOSE_TAG_HISTORY_UPDATE
= 'COMPOSE_TAG_HISTORY_UPDATE';
38 export const COMPOSE_MOUNT
= 'COMPOSE_MOUNT';
39 export const COMPOSE_UNMOUNT
= 'COMPOSE_UNMOUNT';
41 export const COMPOSE_SENSITIVITY_CHANGE
= 'COMPOSE_SENSITIVITY_CHANGE';
42 export const COMPOSE_SPOILERNESS_CHANGE
= 'COMPOSE_SPOILERNESS_CHANGE';
43 export const COMPOSE_SPOILER_TEXT_CHANGE
= 'COMPOSE_SPOILER_TEXT_CHANGE';
44 export const COMPOSE_VISIBILITY_CHANGE
= 'COMPOSE_VISIBILITY_CHANGE';
45 export const COMPOSE_LISTABILITY_CHANGE
= 'COMPOSE_LISTABILITY_CHANGE';
46 export const COMPOSE_COMPOSING_CHANGE
= 'COMPOSE_COMPOSING_CHANGE';
48 export const COMPOSE_EMOJI_INSERT
= 'COMPOSE_EMOJI_INSERT';
50 export const COMPOSE_UPLOAD_CHANGE_REQUEST
= 'COMPOSE_UPLOAD_UPDATE_REQUEST';
51 export const COMPOSE_UPLOAD_CHANGE_SUCCESS
= 'COMPOSE_UPLOAD_UPDATE_SUCCESS';
52 export const COMPOSE_UPLOAD_CHANGE_FAIL
= 'COMPOSE_UPLOAD_UPDATE_FAIL';
54 export function changeCompose(text
) {
61 export function replyCompose(status
, router
) {
62 return (dispatch
, getState
) => {
68 if (!getState().getIn(['compose', 'mounted'])) {
69 router
.push('/statuses/new');
74 export function cancelReplyCompose() {
76 type: COMPOSE_REPLY_CANCEL
,
80 export function resetCompose() {
86 export function mentionCompose(account
, router
) {
87 return (dispatch
, getState
) => {
89 type: COMPOSE_MENTION
,
93 if (!getState().getIn(['compose', 'mounted'])) {
94 router
.push('/statuses/new');
99 export function submitCompose() {
100 return function (dispatch
, getState
) {
101 const status
= getState().getIn(['compose', 'text'], '');
102 const media
= getState().getIn(['compose', 'media_attachments']);
104 if ((!status
|| !status
.length
) && media
.size
=== 0) {
108 dispatch(submitComposeRequest());
110 api(getState
).post('/api/v1/statuses', {
112 in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
113 media_ids: media
.map(item
=> item
.get('id')),
114 sensitive: getState().getIn(['compose', 'sensitive']),
115 spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
116 visibility: getState().getIn(['compose', 'privacy']),
119 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
121 }).then(function (response
) {
122 dispatch(insertIntoTagHistory(response
.data
.tags
));
123 dispatch(submitComposeSuccess({ ...response
.data
}));
125 // To make the app more responsive, immediately get the status into the columns
127 const insertOrRefresh
= (timelineId
, refreshAction
) => {
128 if (getState().getIn(['timelines', timelineId
, 'online'])) {
129 dispatch(updateTimeline(timelineId
, { ...response
.data
}));
130 } else if (getState().getIn(['timelines', timelineId
, 'loaded'])) {
131 dispatch(refreshAction());
135 insertOrRefresh('home', refreshHomeTimeline
);
137 if (response
.data
.in_reply_to_id
=== null && response
.data
.visibility
=== 'public') {
138 insertOrRefresh('community', refreshCommunityTimeline
);
139 insertOrRefresh('public', refreshPublicTimeline
);
141 }).catch(function (error
) {
142 dispatch(submitComposeFail(error
));
147 export function submitComposeRequest() {
149 type: COMPOSE_SUBMIT_REQUEST
,
153 export function submitComposeSuccess(status
) {
155 type: COMPOSE_SUBMIT_SUCCESS
,
160 export function submitComposeFail(error
) {
162 type: COMPOSE_SUBMIT_FAIL
,
167 export function uploadCompose(files
) {
168 return function (dispatch
, getState
) {
169 if (getState().getIn(['compose', 'media_attachments']).size
> 3) {
173 dispatch(uploadComposeRequest());
175 let data
= new FormData();
176 data
.append('file', files
[0]);
178 api(getState
).post('/api/v1/media', data
, {
179 onUploadProgress: function (e
) {
180 dispatch(uploadComposeProgress(e
.loaded
, e
.total
));
182 }).then(function (response
) {
183 dispatch(uploadComposeSuccess(response
.data
));
184 }).catch(function (error
) {
185 dispatch(uploadComposeFail(error
));
190 export function changeUploadCompose(id
, params
) {
191 return (dispatch
, getState
) => {
192 dispatch(changeUploadComposeRequest());
194 api(getState
).put(`/api/v1/media/${id}`, params
).then(response
=> {
195 dispatch(changeUploadComposeSuccess(response
.data
));
197 dispatch(changeUploadComposeFail(id
, error
));
202 export function changeUploadComposeRequest() {
204 type: COMPOSE_UPLOAD_CHANGE_REQUEST
,
208 export function changeUploadComposeSuccess(media
) {
210 type: COMPOSE_UPLOAD_CHANGE_SUCCESS
,
216 export function changeUploadComposeFail(error
) {
218 type: COMPOSE_UPLOAD_CHANGE_FAIL
,
224 export function uploadComposeRequest() {
226 type: COMPOSE_UPLOAD_REQUEST
,
231 export function uploadComposeProgress(loaded
, total
) {
233 type: COMPOSE_UPLOAD_PROGRESS
,
239 export function uploadComposeSuccess(media
) {
241 type: COMPOSE_UPLOAD_SUCCESS
,
247 export function uploadComposeFail(error
) {
249 type: COMPOSE_UPLOAD_FAIL
,
255 export function undoUploadCompose(media_id
) {
257 type: COMPOSE_UPLOAD_UNDO
,
262 export function clearComposeSuggestions() {
263 if (cancelFetchComposeSuggestionsAccounts
) {
264 cancelFetchComposeSuggestionsAccounts();
267 type: COMPOSE_SUGGESTIONS_CLEAR
,
271 const fetchComposeSuggestionsAccounts
= throttle((dispatch
, getState
, token
) => {
272 if (cancelFetchComposeSuggestionsAccounts
) {
273 cancelFetchComposeSuggestionsAccounts();
275 api(getState
).get('/api/v1/accounts/search', {
276 cancelToken: new CancelToken(cancel
=> {
277 cancelFetchComposeSuggestionsAccounts
= cancel
;
284 }).then(response
=> {
285 dispatch(readyComposeSuggestionsAccounts(token
, response
.data
));
287 }, 200, { leading: true, trailing: true });
289 const fetchComposeSuggestionsEmojis
= (dispatch
, getState
, token
) => {
290 const results
= emojiSearch(token
.replace(':', ''), { maxResults: 5 });
291 dispatch(readyComposeSuggestionsEmojis(token
, results
));
294 const fetchComposeSuggestionsTags
= (dispatch
, getState
, token
) => {
295 dispatch(updateSuggestionTags(token
));
298 export function fetchComposeSuggestions(token
) {
299 return (dispatch
, getState
) => {
302 fetchComposeSuggestionsEmojis(dispatch
, getState
, token
);
305 fetchComposeSuggestionsTags(dispatch
, getState
, token
);
308 fetchComposeSuggestionsAccounts(dispatch
, getState
, token
);
314 export function readyComposeSuggestionsEmojis(token
, emojis
) {
316 type: COMPOSE_SUGGESTIONS_READY
,
322 export function readyComposeSuggestionsAccounts(token
, accounts
) {
324 type: COMPOSE_SUGGESTIONS_READY
,
330 export function selectComposeSuggestion(position
, token
, suggestion
) {
331 return (dispatch
, getState
) => {
332 let completion
, startPosition
;
334 if (typeof suggestion
=== 'object' && suggestion
.id
) {
335 completion
= suggestion
.native || suggestion
.colons
;
336 startPosition
= position
- 1;
338 dispatch(useEmoji(suggestion
));
339 } else if (suggestion
[0] === '#') {
340 completion
= suggestion
;
341 startPosition
= position
- 1;
343 completion
= getState().getIn(['accounts', suggestion
, 'acct']);
344 startPosition
= position
;
348 type: COMPOSE_SUGGESTION_SELECT
,
349 position: startPosition
,
356 export function updateSuggestionTags(token
) {
358 type: COMPOSE_SUGGESTION_TAGS_UPDATE
,
363 export function updateTagHistory(tags
) {
365 type: COMPOSE_TAG_HISTORY_UPDATE
,
370 export function hydrateCompose() {
371 return (dispatch
, getState
) => {
372 const me
= getState().getIn(['meta', 'me']);
373 const history
= tagHistory
.get(me
);
375 if (history
!== null) {
376 dispatch(updateTagHistory(history
));
381 function insertIntoTagHistory(tags
) {
382 return (dispatch
, getState
) => {
383 const state
= getState();
384 const oldHistory
= state
.getIn(['compose', 'tagHistory']);
385 const me
= state
.getIn(['meta', 'me']);
386 const names
= tags
.map(({ name
}) => name
);
387 const intersectedOldHistory
= oldHistory
.filter(name
=> !names
.includes(name
));
389 names
.push(...intersectedOldHistory
.toJS());
391 const newHistory
= names
.slice(0, 1000);
393 tagHistory
.set(me
, newHistory
);
394 dispatch(updateTagHistory(newHistory
));
398 export function mountCompose() {
404 export function unmountCompose() {
406 type: COMPOSE_UNMOUNT
,
410 export function changeComposeSensitivity() {
412 type: COMPOSE_SENSITIVITY_CHANGE
,
416 export function changeComposeSpoilerness() {
418 type: COMPOSE_SPOILERNESS_CHANGE
,
422 export function changeComposeSpoilerText(text
) {
424 type: COMPOSE_SPOILER_TEXT_CHANGE
,
429 export function changeComposeVisibility(value
) {
431 type: COMPOSE_VISIBILITY_CHANGE
,
436 export function insertEmojiCompose(position
, emoji
) {
438 type: COMPOSE_EMOJI_INSERT
,
444 export function changeComposing(value
) {
446 type: COMPOSE_COMPOSING_CHANGE
,