2022-06-20 06:46:43 -07:00
import axios , { AxiosError , Canceler } from 'axios' ;
2022-08-18 11:52:53 -07:00
import { List as ImmutableList } from 'immutable' ;
2022-06-20 06:46:43 -07:00
import throttle from 'lodash/throttle' ;
import { defineMessages , IntlShape } from 'react-intl' ;
import api from 'soapbox/api' ;
2022-11-15 09:23:36 -08:00
import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light' ;
2022-06-20 06:46:43 -07:00
import { tagHistory } from 'soapbox/settings' ;
2022-12-20 07:47:46 -08:00
import toast from 'soapbox/toast' ;
2022-06-20 06:46:43 -07:00
import { isLoggedIn } from 'soapbox/utils/auth' ;
import { getFeatures , parseVersion } from 'soapbox/utils/features' ;
2022-06-22 12:40:26 -07:00
import { formatBytes , getVideoDuration } from 'soapbox/utils/media' ;
2022-11-15 12:46:23 -08:00
import resizeImage from 'soapbox/utils/resize-image' ;
2022-06-20 06:46:43 -07:00
import { useEmoji } from './emojis' ;
import { importFetchedAccounts } from './importer' ;
import { uploadMedia , fetchMedia , updateMedia } from './media' ;
import { openModal , closeModal } from './modals' ;
import { getSettings } from './settings' ;
import { createStatus } from './statuses' ;
2022-11-15 08:11:42 -08:00
import type { Emoji } from 'soapbox/components/autosuggest-emoji' ;
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input' ;
2022-06-20 06:46:43 -07:00
import type { AppDispatch , RootState } from 'soapbox/store' ;
2022-08-18 11:52:53 -07:00
import type { Account , APIEntity , Status , Tag } from 'soapbox/types/entities' ;
2023-01-09 13:43:41 -08:00
import type { History } from 'soapbox/types/history' ;
2022-06-20 06:46:43 -07:00
const { CancelToken , isCancel } = axios ;
let cancelFetchComposeSuggestionsAccounts : Canceler ;
const COMPOSE_CHANGE = 'COMPOSE_CHANGE' ;
const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST' ;
const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS' ;
const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL' ;
const COMPOSE_REPLY = 'COMPOSE_REPLY' ;
2022-09-30 14:20:58 -07:00
const COMPOSE_EVENT_REPLY = 'COMPOSE_EVENT_REPLY' ;
2022-06-20 06:46:43 -07:00
const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL' ;
const COMPOSE_QUOTE = 'COMPOSE_QUOTE' ;
const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL' ;
const COMPOSE_DIRECT = 'COMPOSE_DIRECT' ;
const COMPOSE_MENTION = 'COMPOSE_MENTION' ;
const COMPOSE_RESET = 'COMPOSE_RESET' ;
const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST' ;
const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS' ;
const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL' ;
const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS' ;
const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO' ;
const COMPOSE_SUGGESTIONS_CLEAR = 'COMPOSE_SUGGESTIONS_CLEAR' ;
const COMPOSE_SUGGESTIONS_READY = 'COMPOSE_SUGGESTIONS_READY' ;
const COMPOSE_SUGGESTION_SELECT = 'COMPOSE_SUGGESTION_SELECT' ;
const COMPOSE_SUGGESTION_TAGS_UPDATE = 'COMPOSE_SUGGESTION_TAGS_UPDATE' ;
const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE' ;
const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE' ;
const COMPOSE_TYPE_CHANGE = 'COMPOSE_TYPE_CHANGE' ;
const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE' ;
const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE' ;
const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE' ;
const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE' ;
const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT' ;
const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST' ;
const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS' ;
const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL' ;
const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD' ;
const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE' ;
const COMPOSE_POLL_OPTION_ADD = 'COMPOSE_POLL_OPTION_ADD' ;
const COMPOSE_POLL_OPTION_CHANGE = 'COMPOSE_POLL_OPTION_CHANGE' ;
const COMPOSE_POLL_OPTION_REMOVE = 'COMPOSE_POLL_OPTION_REMOVE' ;
const COMPOSE_POLL_SETTINGS_CHANGE = 'COMPOSE_POLL_SETTINGS_CHANGE' ;
const COMPOSE_SCHEDULE_ADD = 'COMPOSE_SCHEDULE_ADD' ;
const COMPOSE_SCHEDULE_SET = 'COMPOSE_SCHEDULE_SET' ;
const COMPOSE_SCHEDULE_REMOVE = 'COMPOSE_SCHEDULE_REMOVE' ;
const COMPOSE_ADD_TO_MENTIONS = 'COMPOSE_ADD_TO_MENTIONS' ;
const COMPOSE_REMOVE_FROM_MENTIONS = 'COMPOSE_REMOVE_FROM_MENTIONS' ;
const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS' ;
const messages = defineMessages ( {
exceededImageSizeLimit : { id : 'upload_error.image_size_limit' , defaultMessage : 'Image exceeds the current file size limit ({limit})' } ,
exceededVideoSizeLimit : { id : 'upload_error.video_size_limit' , defaultMessage : 'Video exceeds the current file size limit ({limit})' } ,
2022-06-22 12:40:26 -07:00
exceededVideoDurationLimit : { id : 'upload_error.video_duration_limit' , defaultMessage : 'Video exceeds the current duration limit ({limit} seconds)' } ,
2022-06-20 06:46:43 -07:00
scheduleError : { id : 'compose.invalid_schedule' , defaultMessage : 'You must schedule a post at least 5 minutes out.' } ,
success : { id : 'compose.submit_success' , defaultMessage : 'Your post was sent' } ,
2022-08-03 14:55:14 -07:00
editSuccess : { id : 'compose.edit_success' , defaultMessage : 'Your post was edited' } ,
2022-06-20 06:46:43 -07:00
uploadErrorLimit : { id : 'upload_error.limit' , defaultMessage : 'File upload limit exceeded.' } ,
uploadErrorPoll : { id : 'upload_error.poll' , defaultMessage : 'File upload not allowed with polls.' } ,
2022-12-20 09:45:46 -08:00
view : { id : 'toast.view' , defaultMessage : 'View' } ,
2022-08-09 15:45:01 -07:00
replyConfirm : { id : 'confirmations.reply.confirm' , defaultMessage : 'Reply' } ,
replyMessage : { id : 'confirmations.reply.message' , defaultMessage : 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' } ,
2022-06-20 06:46:43 -07:00
} ) ;
const setComposeToStatus = ( status : Status , rawText : string , spoilerText? : string , contentType? : string | false , withRedraft? : boolean ) = >
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const { instance } = getState ( ) ;
const { explicitAddressing } = getFeatures ( instance ) ;
dispatch ( {
type : COMPOSE_SET_STATUS ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
status ,
rawText ,
explicitAddressing ,
spoilerText ,
contentType ,
v : parseVersion ( instance . version ) ,
withRedraft ,
} ) ;
} ;
2022-09-10 14:52:06 -07:00
const changeCompose = ( composeId : string , text : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_CHANGE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
text : text ,
} ) ;
const replyCompose = ( status : Status ) = >
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const state = getState ( ) ;
const instance = state . instance ;
const { explicitAddressing } = getFeatures ( instance ) ;
dispatch ( {
type : COMPOSE_REPLY ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
status : status ,
account : state.accounts.get ( state . me ) ,
explicitAddressing ,
} ) ;
dispatch ( openModal ( 'COMPOSE' ) ) ;
} ;
const cancelReplyCompose = ( ) = > ( {
type : COMPOSE_REPLY_CANCEL ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
} ) ;
const quoteCompose = ( status : Status ) = >
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const state = getState ( ) ;
const instance = state . instance ;
const { explicitAddressing } = getFeatures ( instance ) ;
dispatch ( {
type : COMPOSE_QUOTE ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
status : status ,
account : state.accounts.get ( state . me ) ,
explicitAddressing ,
} ) ;
dispatch ( openModal ( 'COMPOSE' ) ) ;
} ;
const cancelQuoteCompose = ( ) = > ( {
type : COMPOSE_QUOTE_CANCEL ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-14 13:05:40 -07:00
const resetCompose = ( composeId = 'compose-modal' ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_RESET ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ) ;
const mentionCompose = ( account : Account ) = >
( dispatch : AppDispatch ) = > {
dispatch ( {
type : COMPOSE_MENTION ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
account : account ,
} ) ;
dispatch ( openModal ( 'COMPOSE' ) ) ;
} ;
const directCompose = ( account : Account ) = >
( dispatch : AppDispatch ) = > {
dispatch ( {
type : COMPOSE_DIRECT ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
account : account ,
} ) ;
dispatch ( openModal ( 'COMPOSE' ) ) ;
} ;
const directComposeById = ( accountId : string ) = >
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const account = getState ( ) . accounts . get ( accountId ) ;
dispatch ( {
type : COMPOSE_DIRECT ,
2022-09-14 13:05:40 -07:00
id : 'compose-modal' ,
2022-06-20 06:46:43 -07:00
account : account ,
} ) ;
dispatch ( openModal ( 'COMPOSE' ) ) ;
} ;
2022-09-10 14:52:06 -07:00
const handleComposeSubmit = ( dispatch : AppDispatch , getState : ( ) = > RootState , composeId : string , data : APIEntity , status : string , edit? : boolean ) = > {
2022-06-20 06:46:43 -07:00
if ( ! dispatch || ! getState ) return ;
2022-09-10 14:52:06 -07:00
dispatch ( insertIntoTagHistory ( composeId , data . tags || [ ] , status ) ) ;
dispatch ( submitComposeSuccess ( composeId , { . . . data } ) ) ;
2022-12-20 07:47:46 -08:00
toast . success ( edit ? messages.editSuccess : messages.success , {
actionLabel : messages.view ,
actionLink : ` /@ ${ data . account . acct } /posts/ ${ data . id } ` ,
} ) ;
2022-06-20 06:46:43 -07:00
} ;
2022-09-10 14:52:06 -07:00
const needsDescriptions = ( state : RootState , composeId : string ) = > {
const media = state . compose . get ( composeId ) ! . media_attachments ;
2022-06-20 06:46:43 -07:00
const missingDescriptionModal = getSettings ( state ) . get ( 'missingDescriptionModal' ) ;
2022-06-20 10:59:51 -07:00
const hasMissing = media . filter ( item = > ! item . description ) . size > 0 ;
2022-06-20 06:46:43 -07:00
return missingDescriptionModal && hasMissing ;
} ;
2022-09-10 14:52:06 -07:00
const validateSchedule = ( state : RootState , composeId : string ) = > {
2022-09-14 11:01:00 -07:00
const schedule = state . compose . get ( composeId ) ? . schedule ;
2022-06-20 06:46:43 -07:00
if ( ! schedule ) return true ;
const fiveMinutesFromNow = new Date ( new Date ( ) . getTime ( ) + 300000 ) ;
return schedule . getTime ( ) > fiveMinutesFromNow . getTime ( ) ;
} ;
2022-09-10 14:52:06 -07:00
const submitCompose = ( composeId : string , routerHistory? : History , force = false ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
if ( ! isLoggedIn ( getState ) ) return ;
const state = getState ( ) ;
2022-09-10 14:52:06 -07:00
const compose = state . compose . get ( composeId ) ! ;
const status = compose . text ;
const media = compose . media_attachments ;
const statusId = compose . id ;
let to = compose . to ;
2022-06-20 06:46:43 -07:00
2022-09-10 14:52:06 -07:00
if ( ! validateSchedule ( state , composeId ) ) {
2022-12-20 08:34:53 -08:00
toast . error ( messages . scheduleError ) ;
2022-06-20 06:46:43 -07:00
return ;
}
if ( ( ! status || ! status . length ) && media . size === 0 ) {
return ;
}
2022-09-10 14:52:06 -07:00
if ( ! force && needsDescriptions ( state , composeId ) ) {
2022-06-20 06:46:43 -07:00
dispatch ( openModal ( 'MISSING_DESCRIPTION' , {
onContinue : ( ) = > {
dispatch ( closeModal ( 'MISSING_DESCRIPTION' ) ) ;
2022-09-10 14:52:06 -07:00
dispatch ( submitCompose ( composeId , routerHistory , true ) ) ;
2022-06-20 06:46:43 -07:00
} ,
} ) ) ;
return ;
}
2022-07-25 15:41:28 -07:00
const mentions : string [ ] | null = status . match ( /(?:^|\s)@([a-z\d_-]+(?:@[^@\s]+)?)/gi ) ;
2022-06-20 06:46:43 -07:00
2022-07-25 10:24:03 -07:00
if ( mentions ) {
to = to . union ( mentions . map ( mention = > mention . trim ( ) . slice ( 1 ) ) ) ;
2022-06-20 06:46:43 -07:00
}
2022-09-10 14:52:06 -07:00
dispatch ( submitComposeRequest ( composeId ) ) ;
2022-06-20 06:46:43 -07:00
dispatch ( closeModal ( ) ) ;
2022-09-10 14:52:06 -07:00
const idempotencyKey = compose . idempotencyKey ;
2022-06-20 06:46:43 -07:00
const params = {
status ,
2022-09-10 14:52:06 -07:00
in_reply_to_id : compose.in_reply_to ,
quote_id : compose.quote ,
2022-06-20 10:59:51 -07:00
media_ids : media.map ( item = > item . id ) ,
2022-09-10 14:52:06 -07:00
sensitive : compose.sensitive ,
spoiler_text : compose.spoiler_text ,
visibility : compose.privacy ,
content_type : compose.content_type ,
poll : compose.poll ,
scheduled_at : compose.schedule ,
2022-06-20 06:46:43 -07:00
to ,
} ;
dispatch ( createStatus ( params , idempotencyKey , statusId ) ) . then ( function ( data ) {
2022-06-21 15:29:17 -07:00
if ( ! statusId && data . visibility === 'direct' && getState ( ) . conversations . mounted <= 0 && routerHistory ) {
2022-06-20 06:46:43 -07:00
routerHistory . push ( '/messages' ) ;
}
2022-09-10 14:52:06 -07:00
handleComposeSubmit ( dispatch , getState , composeId , data , status , ! ! statusId ) ;
2022-06-20 06:46:43 -07:00
} ) . catch ( function ( error ) {
2022-09-10 14:52:06 -07:00
dispatch ( submitComposeFail ( composeId , error ) ) ;
2022-06-20 06:46:43 -07:00
} ) ;
} ;
2022-09-10 14:52:06 -07:00
const submitComposeRequest = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SUBMIT_REQUEST ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-10 14:52:06 -07:00
const submitComposeSuccess = ( composeId : string , status : APIEntity ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SUBMIT_SUCCESS ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
status : status ,
} ) ;
2022-09-10 14:52:06 -07:00
const submitComposeFail = ( composeId : string , error : AxiosError ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SUBMIT_FAIL ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
error : error ,
} ) ;
2022-09-10 14:52:06 -07:00
const uploadCompose = ( composeId : string , files : FileList , intl : IntlShape ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
if ( ! isLoggedIn ( getState ) ) return ;
const attachmentLimit = getState ( ) . instance . configuration . getIn ( [ 'statuses' , 'max_media_attachments' ] ) as number ;
const maxImageSize = getState ( ) . instance . configuration . getIn ( [ 'media_attachments' , 'image_size_limit' ] ) as number | undefined ;
const maxVideoSize = getState ( ) . instance . configuration . getIn ( [ 'media_attachments' , 'video_size_limit' ] ) as number | undefined ;
2022-06-22 12:40:26 -07:00
const maxVideoDuration = getState ( ) . instance . configuration . getIn ( [ 'media_attachments' , 'video_duration_limit' ] ) as number | undefined ;
2022-06-20 06:46:43 -07:00
2022-09-17 12:13:02 -07:00
const media = getState ( ) . compose . get ( composeId ) ? . media_attachments ;
2022-06-20 06:46:43 -07:00
const progress = new Array ( files . length ) . fill ( 0 ) ;
let total = Array . from ( files ) . reduce ( ( a , v ) = > a + v . size , 0 ) ;
2022-09-17 12:13:02 -07:00
const mediaCount = media ? media.size : 0 ;
if ( files . length + mediaCount > attachmentLimit ) {
2022-12-20 09:45:46 -08:00
toast . error ( messages . uploadErrorLimit ) ;
2022-06-20 06:46:43 -07:00
return ;
}
2022-09-14 13:05:40 -07:00
dispatch ( uploadComposeRequest ( composeId ) ) ;
2022-06-20 06:46:43 -07:00
2022-06-22 12:40:26 -07:00
Array . from ( files ) . forEach ( async ( f , i ) = > {
2022-09-17 12:13:02 -07:00
if ( mediaCount + i > attachmentLimit - 1 ) return ;
2022-06-20 06:46:43 -07:00
const isImage = f . type . match ( /image.*/ ) ;
const isVideo = f . type . match ( /video.*/ ) ;
2022-06-22 12:40:26 -07:00
const videoDurationInSeconds = ( isVideo && maxVideoDuration ) ? await getVideoDuration ( f ) : 0 ;
2022-06-20 06:46:43 -07:00
if ( isImage && maxImageSize && ( f . size > maxImageSize ) ) {
const limit = formatBytes ( maxImageSize ) ;
const message = intl . formatMessage ( messages . exceededImageSizeLimit , { limit } ) ;
2022-12-20 08:34:53 -08:00
toast . error ( message ) ;
2022-09-14 13:05:40 -07:00
dispatch ( uploadComposeFail ( composeId , true ) ) ;
2022-06-20 06:46:43 -07:00
return ;
} else if ( isVideo && maxVideoSize && ( f . size > maxVideoSize ) ) {
const limit = formatBytes ( maxVideoSize ) ;
const message = intl . formatMessage ( messages . exceededVideoSizeLimit , { limit } ) ;
2022-12-20 08:34:53 -08:00
toast . error ( message ) ;
2022-09-14 13:05:40 -07:00
dispatch ( uploadComposeFail ( composeId , true ) ) ;
2022-06-20 06:46:43 -07:00
return ;
2022-06-22 12:40:26 -07:00
} else if ( isVideo && maxVideoDuration && ( videoDurationInSeconds > maxVideoDuration ) ) {
const message = intl . formatMessage ( messages . exceededVideoDurationLimit , { limit : maxVideoDuration } ) ;
2022-12-20 08:34:53 -08:00
toast . error ( message ) ;
2022-09-14 13:05:40 -07:00
dispatch ( uploadComposeFail ( composeId , true ) ) ;
2022-06-22 12:40:26 -07:00
return ;
2022-06-20 06:46:43 -07:00
}
// FIXME: Don't define const in loop
/* eslint-disable no-loop-func */
resizeImage ( f ) . then ( file = > {
const data = new FormData ( ) ;
data . append ( 'file' , file ) ;
// Account for disparity in size of original image and resized data
total += file . size - f . size ;
const onUploadProgress = ( { loaded } : any ) = > {
progress [ i ] = loaded ;
2022-09-14 13:05:40 -07:00
dispatch ( uploadComposeProgress ( composeId , progress . reduce ( ( a , v ) = > a + v , 0 ) , total ) ) ;
2022-06-20 06:46:43 -07:00
} ;
return dispatch ( uploadMedia ( data , onUploadProgress ) )
. then ( ( { status , data } ) = > {
// If server-side processing of the media attachment has not completed yet,
// poll the server until it is, before showing the media attachment as uploaded
if ( status === 200 ) {
2022-09-14 13:05:40 -07:00
dispatch ( uploadComposeSuccess ( composeId , data , f ) ) ;
2022-06-20 06:46:43 -07:00
} else if ( status === 202 ) {
const poll = ( ) = > {
dispatch ( fetchMedia ( data . id ) ) . then ( ( { status , data } ) = > {
if ( status === 200 ) {
2022-09-14 13:05:40 -07:00
dispatch ( uploadComposeSuccess ( composeId , data , f ) ) ;
2022-06-20 06:46:43 -07:00
} else if ( status === 206 ) {
setTimeout ( ( ) = > poll ( ) , 1000 ) ;
}
2022-09-14 13:05:40 -07:00
} ) . catch ( error = > dispatch ( uploadComposeFail ( composeId , error ) ) ) ;
2022-06-20 06:46:43 -07:00
} ;
poll ( ) ;
}
} ) ;
2022-09-14 13:05:40 -07:00
} ) . catch ( error = > dispatch ( uploadComposeFail ( composeId , error ) ) ) ;
2022-06-20 06:46:43 -07:00
/* eslint-enable no-loop-func */
} ) ;
} ;
2022-09-14 13:05:40 -07:00
const changeUploadCompose = ( composeId : string , id : string , params : Record < string , any > ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
if ( ! isLoggedIn ( getState ) ) return ;
2022-09-14 13:05:40 -07:00
dispatch ( changeUploadComposeRequest ( composeId ) ) ;
2022-06-20 06:46:43 -07:00
dispatch ( updateMedia ( id , params ) ) . then ( response = > {
2022-09-14 13:05:40 -07:00
dispatch ( changeUploadComposeSuccess ( composeId , response . data ) ) ;
2022-06-20 06:46:43 -07:00
} ) . catch ( error = > {
2022-09-14 13:05:40 -07:00
dispatch ( changeUploadComposeFail ( composeId , id , error ) ) ;
2022-06-20 06:46:43 -07:00
} ) ;
} ;
2022-09-14 13:05:40 -07:00
const changeUploadComposeRequest = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_CHANGE_REQUEST ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
skipLoading : true ,
} ) ;
2022-09-14 13:05:40 -07:00
const changeUploadComposeSuccess = ( composeId : string , media : APIEntity ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_CHANGE_SUCCESS ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
media : media ,
skipLoading : true ,
} ) ;
2022-09-14 13:05:40 -07:00
const changeUploadComposeFail = ( composeId : string , id : string , error : AxiosError ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_CHANGE_FAIL ,
2022-09-14 13:05:40 -07:00
composeId ,
2022-06-20 06:46:43 -07:00
id ,
error : error ,
skipLoading : true ,
} ) ;
2022-09-14 13:05:40 -07:00
const uploadComposeRequest = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_REQUEST ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
skipLoading : true ,
} ) ;
2022-09-14 13:05:40 -07:00
const uploadComposeProgress = ( composeId : string , loaded : number , total : number ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_PROGRESS ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
loaded : loaded ,
total : total ,
} ) ;
2022-09-14 13:05:40 -07:00
const uploadComposeSuccess = ( composeId : string , media : APIEntity , file : File ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_SUCCESS ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
media : media ,
file ,
skipLoading : true ,
} ) ;
2022-09-14 13:05:40 -07:00
const uploadComposeFail = ( composeId : string , error : AxiosError | true ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_FAIL ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
error : error ,
skipLoading : true ,
} ) ;
2022-09-14 13:05:40 -07:00
const undoUploadCompose = ( composeId : string , media_id : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_UPLOAD_UNDO ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
media_id : media_id ,
} ) ;
2022-09-14 13:05:40 -07:00
const clearComposeSuggestions = ( composeId : string ) = > {
2022-06-20 06:46:43 -07:00
if ( cancelFetchComposeSuggestionsAccounts ) {
cancelFetchComposeSuggestionsAccounts ( ) ;
}
return {
type : COMPOSE_SUGGESTIONS_CLEAR ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ;
} ;
2022-09-14 13:05:40 -07:00
const fetchComposeSuggestionsAccounts = throttle ( ( dispatch , getState , composeId , token ) = > {
2022-06-20 06:46:43 -07:00
if ( cancelFetchComposeSuggestionsAccounts ) {
2022-09-14 13:05:40 -07:00
cancelFetchComposeSuggestionsAccounts ( composeId ) ;
2022-06-20 06:46:43 -07:00
}
api ( getState ) . get ( '/api/v1/accounts/search' , {
cancelToken : new CancelToken ( cancel = > {
cancelFetchComposeSuggestionsAccounts = cancel ;
} ) ,
params : {
q : token.slice ( 1 ) ,
resolve : false ,
limit : 4 ,
} ,
} ) . then ( response = > {
dispatch ( importFetchedAccounts ( response . data ) ) ;
2022-09-14 13:05:40 -07:00
dispatch ( readyComposeSuggestionsAccounts ( composeId , token , response . data ) ) ;
2022-06-20 06:46:43 -07:00
} ) . catch ( error = > {
if ( ! isCancel ( error ) ) {
2022-12-20 09:45:46 -08:00
toast . showAlertForError ( error ) ;
2022-06-20 06:46:43 -07:00
}
} ) ;
} , 200 , { leading : true , trailing : true } ) ;
2022-09-14 13:05:40 -07:00
const fetchComposeSuggestionsEmojis = ( dispatch : AppDispatch , getState : ( ) = > RootState , composeId : string , token : string ) = > {
2022-06-20 06:46:43 -07:00
const results = emojiSearch ( token . replace ( ':' , '' ) , { maxResults : 5 } as any ) ;
2022-09-14 13:05:40 -07:00
dispatch ( readyComposeSuggestionsEmojis ( composeId , token , results ) ) ;
2022-06-20 06:46:43 -07:00
} ;
2022-09-14 13:05:40 -07:00
const fetchComposeSuggestionsTags = ( dispatch : AppDispatch , getState : ( ) = > RootState , composeId : string , token : string ) = > {
2022-08-18 11:52:53 -07:00
const state = getState ( ) ;
const currentTrends = state . trends . items ;
2022-09-14 13:05:40 -07:00
dispatch ( updateSuggestionTags ( composeId , token , currentTrends ) ) ;
2022-06-20 06:46:43 -07:00
} ;
2022-09-14 13:05:40 -07:00
const fetchComposeSuggestions = ( composeId : string , token : string ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
switch ( token [ 0 ] ) {
case ':' :
2022-09-14 13:05:40 -07:00
fetchComposeSuggestionsEmojis ( dispatch , getState , composeId , token ) ;
2022-06-20 06:46:43 -07:00
break ;
case '#' :
2022-09-14 13:05:40 -07:00
fetchComposeSuggestionsTags ( dispatch , getState , composeId , token ) ;
2022-06-20 06:46:43 -07:00
break ;
default :
2022-09-14 13:05:40 -07:00
fetchComposeSuggestionsAccounts ( dispatch , getState , composeId , token ) ;
2022-06-20 06:46:43 -07:00
break ;
}
} ;
2022-09-14 13:05:40 -07:00
const readyComposeSuggestionsEmojis = ( composeId : string , token : string , emojis : Emoji [ ] ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SUGGESTIONS_READY ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
token ,
emojis ,
} ) ;
2022-09-14 13:05:40 -07:00
const readyComposeSuggestionsAccounts = ( composeId : string , token : string , accounts : APIEntity [ ] ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SUGGESTIONS_READY ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
token ,
accounts ,
} ) ;
2022-09-14 13:05:40 -07:00
const selectComposeSuggestion = ( composeId : string , position : number , token : string | null , suggestion : AutoSuggestion , path : Array < string | number > ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
let completion , startPosition ;
if ( typeof suggestion === 'object' && suggestion . id ) {
completion = suggestion . native || suggestion . colons ;
startPosition = position - 1 ;
dispatch ( useEmoji ( suggestion ) ) ;
} else if ( typeof suggestion === 'string' && suggestion [ 0 ] === '#' ) {
completion = suggestion ;
startPosition = position - 1 ;
} else {
completion = getState ( ) . accounts . get ( suggestion ) ! . acct ;
startPosition = position ;
}
dispatch ( {
type : COMPOSE_SUGGESTION_SELECT ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
position : startPosition ,
token ,
completion ,
path ,
} ) ;
} ;
2022-09-14 13:05:40 -07:00
const updateSuggestionTags = ( composeId : string , token : string , currentTrends : ImmutableList < Tag > ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SUGGESTION_TAGS_UPDATE ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
token ,
2022-08-18 11:52:53 -07:00
currentTrends ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-14 13:05:40 -07:00
const updateTagHistory = ( composeId : string , tags : string [ ] ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_TAG_HISTORY_UPDATE ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
tags ,
} ) ;
2022-09-10 14:52:06 -07:00
const insertIntoTagHistory = ( composeId : string , recognizedTags : APIEntity [ ] , text : string ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const state = getState ( ) ;
2022-09-10 14:52:06 -07:00
const oldHistory = state . compose . get ( composeId ) ! . tagHistory ;
2022-06-20 06:46:43 -07:00
const me = state . me ;
const names = recognizedTags
. filter ( tag = > text . match ( new RegExp ( ` # ${ tag . name } ` , 'i' ) ) )
. map ( tag = > tag . name ) ;
const intersectedOldHistory = oldHistory . filter ( name = > names . findIndex ( newName = > newName . toLowerCase ( ) === name . toLowerCase ( ) ) === - 1 ) ;
names . push ( . . . intersectedOldHistory . toJS ( ) ) ;
const newHistory = names . slice ( 0 , 1000 ) ;
tagHistory . set ( me as string , newHistory ) ;
2022-09-14 13:05:40 -07:00
dispatch ( updateTagHistory ( composeId , newHistory ) ) ;
2022-06-20 06:46:43 -07:00
} ;
2022-09-10 14:52:06 -07:00
const changeComposeSpoilerness = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SPOILERNESS_CHANGE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-10 14:52:06 -07:00
const changeComposeContentType = ( composeId : string , value : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_TYPE_CHANGE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
value ,
} ) ;
2022-09-10 14:52:06 -07:00
const changeComposeSpoilerText = ( composeId : string , text : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SPOILER_TEXT_CHANGE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
text ,
} ) ;
2022-09-10 14:52:06 -07:00
const changeComposeVisibility = ( composeId : string , value : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_VISIBILITY_CHANGE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
value ,
} ) ;
2022-09-10 14:52:06 -07:00
const insertEmojiCompose = ( composeId : string , position : number , emoji : Emoji , needsSpace : boolean ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_EMOJI_INSERT ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
position ,
emoji ,
needsSpace ,
} ) ;
2022-09-10 14:52:06 -07:00
const addPoll = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_POLL_ADD ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-10 14:52:06 -07:00
const removePoll = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_POLL_REMOVE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-10 14:52:06 -07:00
const addSchedule = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SCHEDULE_ADD ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-10 14:52:06 -07:00
const setSchedule = ( composeId : string , date : Date ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SCHEDULE_SET ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
date : date ,
} ) ;
2022-09-10 14:52:06 -07:00
const removeSchedule = ( composeId : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_SCHEDULE_REMOVE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
} ) ;
2022-09-10 14:52:06 -07:00
const addPollOption = ( composeId : string , title : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_POLL_OPTION_ADD ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
title ,
} ) ;
2022-09-10 14:52:06 -07:00
const changePollOption = ( composeId : string , index : number , title : string ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_POLL_OPTION_CHANGE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
index ,
title ,
} ) ;
2022-09-10 14:52:06 -07:00
const removePollOption = ( composeId : string , index : number ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_POLL_OPTION_REMOVE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
index ,
} ) ;
2022-09-10 14:52:06 -07:00
const changePollSettings = ( composeId : string , expiresIn? : string | number , isMultiple? : boolean ) = > ( {
2022-06-20 06:46:43 -07:00
type : COMPOSE_POLL_SETTINGS_CHANGE ,
2022-09-10 14:52:06 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
expiresIn ,
isMultiple ,
} ) ;
2022-09-10 14:52:06 -07:00
const openComposeWithText = ( composeId : string , text = '' ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch ) = > {
2022-09-14 13:05:40 -07:00
dispatch ( resetCompose ( composeId ) ) ;
2022-06-20 06:46:43 -07:00
dispatch ( openModal ( 'COMPOSE' ) ) ;
2022-09-10 14:52:06 -07:00
dispatch ( changeCompose ( composeId , text ) ) ;
2022-06-20 06:46:43 -07:00
} ;
2022-09-14 13:05:40 -07:00
const addToMentions = ( composeId : string , accountId : string ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const state = getState ( ) ;
const acct = state . accounts . get ( accountId ) ! . acct ;
return dispatch ( {
type : COMPOSE_ADD_TO_MENTIONS ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
account : acct ,
} ) ;
} ;
2022-09-14 13:05:40 -07:00
const removeFromMentions = ( composeId : string , accountId : string ) = >
2022-06-20 06:46:43 -07:00
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const state = getState ( ) ;
const acct = state . accounts . get ( accountId ) ! . acct ;
return dispatch ( {
type : COMPOSE_REMOVE_FROM_MENTIONS ,
2022-09-14 13:05:40 -07:00
id : composeId ,
2022-06-20 06:46:43 -07:00
account : acct ,
} ) ;
} ;
2022-09-16 12:02:15 -07:00
const eventDiscussionCompose = ( composeId : string , status : Status ) = >
( dispatch : AppDispatch , getState : ( ) = > RootState ) = > {
const state = getState ( ) ;
const instance = state . instance ;
const { explicitAddressing } = getFeatures ( instance ) ;
dispatch ( {
2022-09-30 14:20:58 -07:00
type : COMPOSE_EVENT_REPLY ,
2022-09-16 12:02:15 -07:00
id : composeId ,
status : status ,
account : state.accounts.get ( state . me ) ,
explicitAddressing ,
} ) ;
} ;
2022-06-20 06:46:43 -07:00
export {
COMPOSE_CHANGE ,
COMPOSE_SUBMIT_REQUEST ,
COMPOSE_SUBMIT_SUCCESS ,
COMPOSE_SUBMIT_FAIL ,
COMPOSE_REPLY ,
COMPOSE_REPLY_CANCEL ,
2022-09-30 14:20:58 -07:00
COMPOSE_EVENT_REPLY ,
2022-06-20 06:46:43 -07:00
COMPOSE_QUOTE ,
COMPOSE_QUOTE_CANCEL ,
COMPOSE_DIRECT ,
COMPOSE_MENTION ,
COMPOSE_RESET ,
COMPOSE_UPLOAD_REQUEST ,
COMPOSE_UPLOAD_SUCCESS ,
COMPOSE_UPLOAD_FAIL ,
COMPOSE_UPLOAD_PROGRESS ,
COMPOSE_UPLOAD_UNDO ,
COMPOSE_SUGGESTIONS_CLEAR ,
COMPOSE_SUGGESTIONS_READY ,
COMPOSE_SUGGESTION_SELECT ,
COMPOSE_SUGGESTION_TAGS_UPDATE ,
COMPOSE_TAG_HISTORY_UPDATE ,
COMPOSE_SPOILERNESS_CHANGE ,
COMPOSE_TYPE_CHANGE ,
COMPOSE_SPOILER_TEXT_CHANGE ,
COMPOSE_VISIBILITY_CHANGE ,
COMPOSE_LISTABILITY_CHANGE ,
COMPOSE_COMPOSING_CHANGE ,
COMPOSE_EMOJI_INSERT ,
COMPOSE_UPLOAD_CHANGE_REQUEST ,
COMPOSE_UPLOAD_CHANGE_SUCCESS ,
COMPOSE_UPLOAD_CHANGE_FAIL ,
COMPOSE_POLL_ADD ,
COMPOSE_POLL_REMOVE ,
COMPOSE_POLL_OPTION_ADD ,
COMPOSE_POLL_OPTION_CHANGE ,
COMPOSE_POLL_OPTION_REMOVE ,
COMPOSE_POLL_SETTINGS_CHANGE ,
COMPOSE_SCHEDULE_ADD ,
COMPOSE_SCHEDULE_SET ,
COMPOSE_SCHEDULE_REMOVE ,
COMPOSE_ADD_TO_MENTIONS ,
COMPOSE_REMOVE_FROM_MENTIONS ,
COMPOSE_SET_STATUS ,
setComposeToStatus ,
changeCompose ,
replyCompose ,
cancelReplyCompose ,
quoteCompose ,
cancelQuoteCompose ,
resetCompose ,
mentionCompose ,
directCompose ,
directComposeById ,
handleComposeSubmit ,
submitCompose ,
submitComposeRequest ,
submitComposeSuccess ,
submitComposeFail ,
uploadCompose ,
changeUploadCompose ,
changeUploadComposeRequest ,
changeUploadComposeSuccess ,
changeUploadComposeFail ,
uploadComposeRequest ,
uploadComposeProgress ,
uploadComposeSuccess ,
uploadComposeFail ,
undoUploadCompose ,
clearComposeSuggestions ,
fetchComposeSuggestions ,
readyComposeSuggestionsEmojis ,
readyComposeSuggestionsAccounts ,
selectComposeSuggestion ,
updateSuggestionTags ,
updateTagHistory ,
changeComposeSpoilerness ,
changeComposeContentType ,
changeComposeSpoilerText ,
changeComposeVisibility ,
insertEmojiCompose ,
addPoll ,
removePoll ,
addSchedule ,
setSchedule ,
removeSchedule ,
addPollOption ,
changePollOption ,
removePollOption ,
changePollSettings ,
openComposeWithText ,
addToMentions ,
removeFromMentions ,
2022-09-16 12:02:15 -07:00
eventDiscussionCompose ,
2022-06-20 06:46:43 -07:00
} ;