import api from '../api'; import emojione from 'emojione'; import { updateTimeline, refreshHomeTimeline, refreshCommunityTimeline, refreshPublicTimeline, } from './timelines'; export const COMPOSE_CHANGE = 'COMPOSE_CHANGE'; export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS'; export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; export const COMPOSE_MENTION = 'COMPOSE_MENTION'; export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'; export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL'; export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS'; 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_SUGGESTIONS_READY_TXT = 'COMPOSE_SUGGESTIONS_READY_TXT'; export const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT'; export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; export const COMPOSE_ADVANCED_OPTIONS_CHANGE = 'COMPOSE_ADVANCED_OPTIONS_CHANGE'; export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE'; export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; export function changeCompose(text) { return { type: COMPOSE_CHANGE, text: text, }; }; export function replyCompose(status, router) { return (dispatch, getState) => { dispatch({ type: COMPOSE_REPLY, status: status, }); if (!getState().getIn(['compose', 'mounted'])) { router.push('/statuses/new'); } }; }; export function cancelReplyCompose() { return { type: COMPOSE_REPLY_CANCEL, }; }; export function mentionCompose(account, router) { return (dispatch, getState) => { dispatch({ type: COMPOSE_MENTION, account: account, }); if (!getState().getIn(['compose', 'mounted'])) { router.push('/statuses/new'); } }; }; export function submitCompose() { return function (dispatch, getState) { let status = getState().getIn(['compose', 'text'], ''); if (!status || !status.length) { return; } dispatch(submitComposeRequest()); if (getState().getIn(['compose', 'advanced_options', 'do_not_federate'])) { status = status + ' 👁️'; } api(getState).post('/api/v1/statuses', { status, in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), sensitive: getState().getIn(['compose', 'sensitive']), spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), visibility: getState().getIn(['compose', 'privacy']), }, { headers: { 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), }, }).then(function (response) { dispatch(submitComposeSuccess({ ...response.data })); // To make the app more responsive, immediately get the status into the columns const insertOrRefresh = (timelineId, refreshAction) => { if (getState().getIn(['timelines', timelineId, 'online'])) { dispatch(updateTimeline(timelineId, { ...response.data })); } else if (getState().getIn(['timelines', timelineId, 'loaded'])) { dispatch(refreshAction()); } }; insertOrRefresh('home', refreshHomeTimeline); if (response.data.in_reply_to_id === null && response.data.visibility === 'public') { insertOrRefresh('community', refreshCommunityTimeline); insertOrRefresh('public', refreshPublicTimeline); } }).catch(function (error) { dispatch(submitComposeFail(error)); }); }; }; export function submitComposeRequest() { return { type: COMPOSE_SUBMIT_REQUEST, }; }; export function submitComposeSuccess(status) { return { type: COMPOSE_SUBMIT_SUCCESS, status: status, }; }; export function submitComposeFail(error) { return { type: COMPOSE_SUBMIT_FAIL, error: error, }; }; export function uploadCompose(files) { return function (dispatch, getState) { if (getState().getIn(['compose', 'media_attachments']).size > 3) { return; } dispatch(uploadComposeRequest()); let data = new FormData(); data.append('file', files[0]); api(getState).post('/api/v1/media', data, { onUploadProgress: function (e) { dispatch(uploadComposeProgress(e.loaded, e.total)); }, }).then(function (response) { dispatch(uploadComposeSuccess(response.data)); }).catch(function (error) { dispatch(uploadComposeFail(error)); }); }; }; export function uploadComposeRequest() { return { type: COMPOSE_UPLOAD_REQUEST, skipLoading: true, }; }; export function uploadComposeProgress(loaded, total) { return { type: COMPOSE_UPLOAD_PROGRESS, loaded: loaded, total: total, }; }; export function uploadComposeSuccess(media) { return { type: COMPOSE_UPLOAD_SUCCESS, media: media, skipLoading: true, }; }; export function uploadComposeFail(error) { return { type: COMPOSE_UPLOAD_FAIL, error: error, skipLoading: true, }; }; export function undoUploadCompose(media_id) { return { type: COMPOSE_UPLOAD_UNDO, media_id: media_id, }; }; export function clearComposeSuggestions() { return { type: COMPOSE_SUGGESTIONS_CLEAR, }; }; let allShortcodes = null; // cached list of all shortcodes for suggestions export function fetchComposeSuggestions(token) { let leading = token[0]; if (leading === '@') { // handle search return (dispatch, getState) => { api(getState).get('/api/v1/accounts/search', { params: { q: token.slice(1), // remove the '@' resolve: false, limit: 4, }, }).then(response => { dispatch(readyComposeSuggestions(token, response.data)); }); }; } else if (leading === ':') { // shortcode if (!allShortcodes) { allShortcodes = Object.keys(emojione.emojioneList); // TODO when we have custom emojons merged, add them to this shortcode list } return (dispatch) => { dispatch(readyComposeSuggestionsTxt(token, allShortcodes.filter((sc) => { return sc.indexOf(token) === 0; }))); }; } else { // hashtag return (dispatch, getState) => { api(getState).get('/api/v1/search', { params: { q: token, resolve: true, }, }).then(response => { dispatch(readyComposeSuggestionsTxt(token, response.data.hashtags.map((ht) => `#${ht}`))); }); }; } }; export function readyComposeSuggestions(token, accounts) { return { type: COMPOSE_SUGGESTIONS_READY, token, accounts, }; }; export function readyComposeSuggestionsTxt(token, items) { return { type: COMPOSE_SUGGESTIONS_READY_TXT, token, items, }; }; export function selectComposeSuggestion(position, token, accountId) { return (dispatch, getState) => { const completion = (typeof accountId === 'string') ? accountId.slice(1) : // text suggestion: discard the leading : or # - the replacing code replaces only what follows getState().getIn(['accounts', accountId, 'acct']); dispatch({ type: COMPOSE_SUGGESTION_SELECT, position, token, completion, }); }; }; export function mountCompose() { return { type: COMPOSE_MOUNT, }; }; export function unmountCompose() { return { type: COMPOSE_UNMOUNT, }; }; export function toggleComposeAdvancedOption(option) { return { type: COMPOSE_ADVANCED_OPTIONS_CHANGE, option: option, }; } export function changeComposeSensitivity() { return { type: COMPOSE_SENSITIVITY_CHANGE, }; }; export function changeComposeSpoilerness() { return { type: COMPOSE_SPOILERNESS_CHANGE, }; }; export function changeComposeSpoilerText(text) { return { type: COMPOSE_SPOILER_TEXT_CHANGE, text, }; }; export function changeComposeVisibility(value) { return { type: COMPOSE_VISIBILITY_CHANGE, value, }; }; export function insertEmojiCompose(position, emoji) { return { type: COMPOSE_EMOJI_INSERT, position, emoji, }; }; export function changeComposing(value) { return { type: COMPOSE_COMPOSING_CHANGE, value, }; }