2021-09-12 09:25:44 -07:00
import { OrderedSet as ImmutableOrderedSet } from 'immutable' ;
2020-03-27 13:59:38 -07:00
import React from 'react' ;
import { connect } from 'react-redux' ;
import PropTypes from 'prop-types' ;
import classNames from 'classnames' ;
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2021-11-04 11:16:28 -07:00
import { fetchStatusWithContext } from '../../actions/statuses' ;
2020-03-27 13:59:38 -07:00
import MissingIndicator from '../../components/missing_indicator' ;
import DetailedStatus from './components/detailed_status' ;
import ActionBar from './components/action_bar' ;
2021-11-03 18:35:40 -07:00
import Column from 'soapbox/components/column' ;
2020-03-27 13:59:38 -07:00
import {
favourite ,
unfavourite ,
reblog ,
unreblog ,
2020-08-30 22:09:02 -07:00
bookmark ,
unbookmark ,
2020-03-27 13:59:38 -07:00
pin ,
unpin ,
} from '../../actions/interactions' ;
2020-05-22 19:15:07 -07:00
import { simpleEmojiReact } from '../../actions/emoji_reacts' ;
2020-03-27 13:59:38 -07:00
import {
replyCompose ,
mentionCompose ,
directCompose ,
} from '../../actions/compose' ;
import { blockAccount } from '../../actions/accounts' ;
import {
muteStatus ,
unmuteStatus ,
deleteStatus ,
hideStatus ,
revealStatus ,
} from '../../actions/statuses' ;
import { initMuteModal } from '../../actions/mutes' ;
import { initReport } from '../../actions/reports' ;
import { makeGetStatus } from '../../selectors' ;
2021-09-12 17:36:18 -07:00
// import ColumnHeader from '../../components/column_header';
2020-03-27 13:59:38 -07:00
import { openModal } from '../../actions/modal' ;
import { defineMessages , injectIntl , FormattedMessage } from 'react-intl' ;
import ImmutablePureComponent from 'react-immutable-pure-component' ;
2021-08-02 08:46:18 -07:00
import { createSelector } from 'reselect' ;
2020-03-27 13:59:38 -07:00
import { HotKeys } from 'react-hotkeys' ;
import { attachFullscreenListener , detachFullscreenListener , isFullscreen } from '../ui/util/fullscreen' ;
import { textForScreenReader , defaultMediaVisibility } from '../../components/status' ;
2021-09-12 17:36:18 -07:00
// import Icon from 'soapbox/components/icon';
2020-05-28 15:52:07 -07:00
import { getSettings } from 'soapbox/actions/settings' ;
2020-12-24 16:17:53 -08:00
import { getSoapboxConfig } from 'soapbox/actions/soapbox' ;
2021-01-18 18:59:07 -08:00
import { deactivateUserModal , deleteUserModal , deleteStatusModal , toggleStatusSensitivityModal } from 'soapbox/actions/moderation' ;
2021-10-06 15:50:43 -07:00
import ThreadStatus from './components/thread_status' ;
2021-10-09 15:47:25 -07:00
import PendingStatus from 'soapbox/features/ui/components/pending_status' ;
2021-09-27 11:38:02 -07:00
import SubNavigation from 'soapbox/components/sub_navigation' ;
2021-10-13 11:55:02 -07:00
import { launchChat } from 'soapbox/actions/chats' ;
2021-11-04 10:34:22 -07:00
import PullToRefresh from 'soapbox/components/pull_to_refresh' ;
2020-03-27 13:59:38 -07:00
const messages = defineMessages ( {
2021-09-27 11:38:02 -07:00
title : { id : 'status.title' , defaultMessage : 'Post' } ,
2021-10-14 06:49:33 -07:00
titleDirect : { id : 'status.title_direct' , defaultMessage : 'Direct message' } ,
2020-03-27 13:59:38 -07:00
deleteConfirm : { id : 'confirmations.delete.confirm' , defaultMessage : 'Delete' } ,
2021-12-30 08:38:57 -08:00
deleteHeading : { id : 'confirmations.delete.heading' , defaultMessage : 'Delete' } ,
2020-03-27 13:59:38 -07:00
deleteMessage : { id : 'confirmations.delete.message' , defaultMessage : 'Are you sure you want to delete this post?' } ,
redraftConfirm : { id : 'confirmations.redraft.confirm' , defaultMessage : 'Delete & redraft' } ,
2021-12-30 08:38:57 -08:00
redraftHeading : { id : 'confirmations.redraft.heading' , defaultMessage : 'Delete & redraft' } ,
2020-03-27 13:59:38 -07:00
redraftMessage : { id : 'confirmations.redraft.message' , defaultMessage : 'Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.' } ,
blockConfirm : { id : 'confirmations.block.confirm' , defaultMessage : 'Block' } ,
revealAll : { id : 'status.show_more_all' , defaultMessage : 'Show more for all' } ,
hideAll : { id : 'status.show_less_all' , defaultMessage : 'Show less for all' } ,
detailedStatus : { id : 'status.detailed_status' , defaultMessage : 'Detailed conversation view' } ,
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?' } ,
blockAndReport : { id : 'confirmations.block.block_and_report' , defaultMessage : 'Block & Report' } ,
} ) ;
const makeMapStateToProps = ( ) => {
const getStatus = makeGetStatus ( ) ;
2021-08-02 08:46:18 -07:00
const getAncestorsIds = createSelector ( [
( _ , { id } ) => id ,
state => state . getIn ( [ 'contexts' , 'inReplyTos' ] ) ,
] , ( statusId , inReplyTos ) => {
2021-09-12 09:25:44 -07:00
let ancestorsIds = ImmutableOrderedSet ( ) ;
2021-08-03 01:50:08 -07:00
let id = statusId ;
2021-08-02 08:46:18 -07:00
2021-11-23 12:55:40 -08:00
while ( id && ! ancestorsIds . includes ( id ) ) {
2021-09-12 09:25:44 -07:00
ancestorsIds = ImmutableOrderedSet ( [ id ] ) . union ( ancestorsIds ) ;
2021-08-03 01:50:08 -07:00
id = inReplyTos . get ( id ) ;
}
2020-03-27 13:59:38 -07:00
2021-08-02 08:46:18 -07:00
return ancestorsIds ;
} ) ;
const getDescendantsIds = createSelector ( [
( _ , { id } ) => id ,
state => state . getIn ( [ 'contexts' , 'replies' ] ) ,
] , ( statusId , contextReplies ) => {
2021-09-12 09:25:44 -07:00
let descendantsIds = ImmutableOrderedSet ( ) ;
2021-08-03 01:50:08 -07:00
const ids = [ statusId ] ;
2020-03-27 13:59:38 -07:00
2021-08-03 01:50:08 -07:00
while ( ids . length > 0 ) {
2021-08-03 10:10:42 -07:00
const id = ids . shift ( ) ;
2021-08-03 01:50:08 -07:00
const replies = contextReplies . get ( id ) ;
2020-03-27 13:59:38 -07:00
2021-11-23 12:55:40 -08:00
if ( descendantsIds . includes ( id ) ) {
break ;
}
2021-08-03 01:50:08 -07:00
if ( statusId !== id ) {
descendantsIds = descendantsIds . union ( [ id ] ) ;
}
2020-03-27 13:59:38 -07:00
2021-08-03 01:50:08 -07:00
if ( replies ) {
replies . reverse ( ) . forEach ( reply => {
ids . unshift ( reply ) ;
} ) ;
2021-08-02 08:46:18 -07:00
}
2021-08-03 01:50:08 -07:00
}
2020-03-27 13:59:38 -07:00
2021-08-02 08:46:18 -07:00
return descendantsIds ;
} ) ;
2020-03-27 13:59:38 -07:00
2021-08-02 08:46:18 -07:00
const mapStateToProps = ( state , props ) => {
const status = getStatus ( state , { id : props . params . statusId } ) ;
2021-09-12 09:25:44 -07:00
let ancestorsIds = ImmutableOrderedSet ( ) ;
let descendantsIds = ImmutableOrderedSet ( ) ;
2020-03-27 13:59:38 -07:00
2021-08-02 08:46:18 -07:00
if ( status ) {
2021-11-23 12:55:40 -08:00
const statusId = status . get ( 'id' ) ;
ancestorsIds = getAncestorsIds ( state , { id : state . getIn ( [ 'contexts' , 'inReplyTos' , statusId ] ) } ) ;
descendantsIds = getDescendantsIds ( state , { id : statusId } ) ;
ancestorsIds = ancestorsIds . delete ( statusId ) . subtract ( descendantsIds ) ;
descendantsIds = descendantsIds . delete ( statusId ) . subtract ( ancestorsIds ) ;
2020-03-27 13:59:38 -07:00
}
2021-06-30 19:39:27 -07:00
const soapbox = getSoapboxConfig ( state ) ;
2020-03-27 13:59:38 -07:00
return {
status ,
ancestorsIds ,
descendantsIds ,
askReplyConfirmation : state . getIn ( [ 'compose' , 'text' ] ) . trim ( ) . length !== 0 ,
domain : state . getIn ( [ 'meta' , 'domain' ] ) ,
2020-04-01 19:20:47 -07:00
me : state . get ( 'me' ) ,
2020-10-29 07:41:43 -07:00
displayMedia : getSettings ( state ) . get ( 'displayMedia' ) ,
2021-06-30 19:39:27 -07:00
allowedEmoji : soapbox . get ( 'allowedEmoji' ) ,
2020-03-27 13:59:38 -07:00
} ;
} ;
return mapStateToProps ;
} ;
export default @ injectIntl
@ connect ( makeMapStateToProps )
class Status extends ImmutablePureComponent {
static contextTypes = {
router : PropTypes . object ,
} ;
static propTypes = {
params : PropTypes . object . isRequired ,
dispatch : PropTypes . func . isRequired ,
status : ImmutablePropTypes . map ,
2021-09-11 12:24:54 -07:00
ancestorsIds : ImmutablePropTypes . orderedSet ,
descendantsIds : ImmutablePropTypes . orderedSet ,
2020-03-27 13:59:38 -07:00
intl : PropTypes . object . isRequired ,
askReplyConfirmation : PropTypes . bool ,
2020-05-16 11:57:22 -07:00
domain : PropTypes . string ,
2020-10-29 07:41:43 -07:00
displayMedia : PropTypes . string ,
2020-03-27 13:59:38 -07:00
} ;
state = {
fullscreen : false ,
2020-10-29 07:41:43 -07:00
showMedia : defaultMediaVisibility ( this . props . status , this . props . displayMedia ) ,
2020-03-27 13:59:38 -07:00
loadedStatusId : undefined ,
2021-07-21 04:58:22 -07:00
emojiSelectorFocused : false ,
2020-03-27 13:59:38 -07:00
} ;
2021-11-04 11:16:28 -07:00
fetchData = ( ) => {
2021-11-04 10:34:22 -07:00
const { dispatch , params } = this . props ;
2021-11-04 11:16:28 -07:00
const { statusId } = params ;
return dispatch ( fetchStatusWithContext ( statusId ) ) ;
2021-11-04 10:34:22 -07:00
}
2020-04-14 14:47:35 -07:00
componentDidMount ( ) {
2021-11-04 11:16:28 -07:00
this . fetchData ( ) ;
2020-03-27 13:59:38 -07:00
attachFullscreenListener ( this . onFullScreenChange ) ;
}
handleToggleMediaVisibility = ( ) => {
this . setState ( { showMedia : ! this . state . showMedia } ) ;
}
2020-05-20 13:52:46 -07:00
handleEmojiReactClick = ( status , emoji ) => {
2020-05-22 19:15:07 -07:00
this . props . dispatch ( simpleEmojiReact ( status , emoji ) ) ;
2020-05-20 13:52:46 -07:00
}
2020-03-27 13:59:38 -07:00
handleFavouriteClick = ( status ) => {
if ( status . get ( 'favourited' ) ) {
this . props . dispatch ( unfavourite ( status ) ) ;
} else {
this . props . dispatch ( favourite ( status ) ) ;
}
}
handlePin = ( status ) => {
if ( status . get ( 'pinned' ) ) {
this . props . dispatch ( unpin ( status ) ) ;
} else {
this . props . dispatch ( pin ( status ) ) ;
}
}
2020-08-30 22:09:02 -07:00
handleBookmark = ( status ) => {
if ( status . get ( 'bookmarked' ) ) {
2021-06-26 15:04:27 -07:00
this . props . dispatch ( unbookmark ( this . props . intl , status ) ) ;
2020-08-30 22:09:02 -07:00
} else {
2021-06-26 15:04:27 -07:00
this . props . dispatch ( bookmark ( this . props . intl , status ) ) ;
2020-08-30 22:09:02 -07:00
}
}
2020-03-27 13:59:38 -07:00
handleReplyClick = ( status ) => {
2021-08-03 10:10:42 -07:00
const { askReplyConfirmation , dispatch , intl } = this . props ;
2020-03-27 13:59:38 -07:00
if ( askReplyConfirmation ) {
dispatch ( openModal ( 'CONFIRM' , {
message : intl . formatMessage ( messages . replyMessage ) ,
confirm : intl . formatMessage ( messages . replyConfirm ) ,
onConfirm : ( ) => dispatch ( replyCompose ( status , this . context . router . history ) ) ,
} ) ) ;
} else {
dispatch ( replyCompose ( status , this . context . router . history ) ) ;
}
}
handleModalReblog = ( status ) => {
this . props . dispatch ( reblog ( status ) ) ;
}
handleReblogClick = ( status , e ) => {
2020-04-21 12:41:13 -07:00
this . props . dispatch ( ( _ , getState ) => {
2020-04-28 11:49:39 -07:00
const boostModal = getSettings ( getState ( ) ) . get ( 'boostModal' ) ;
2020-04-21 12:41:13 -07:00
if ( status . get ( 'reblogged' ) ) {
this . props . dispatch ( unreblog ( status ) ) ;
2020-03-27 13:59:38 -07:00
} else {
2020-04-21 12:41:13 -07:00
if ( ( e && e . shiftKey ) || ! boostModal ) {
this . handleModalReblog ( status ) ;
} else {
this . props . dispatch ( openModal ( 'BOOST' , { status , onReblog : this . handleModalReblog } ) ) ;
}
2020-03-27 13:59:38 -07:00
}
2020-04-21 12:41:13 -07:00
} ) ;
2020-03-27 13:59:38 -07:00
}
handleDeleteClick = ( status , history , withRedraft = false ) => {
const { dispatch , intl } = this . props ;
2020-04-21 12:41:13 -07:00
this . props . dispatch ( ( _ , getState ) => {
2020-04-28 11:49:39 -07:00
const deleteModal = getSettings ( getState ( ) ) . get ( 'deleteModal' ) ;
2020-04-21 12:41:13 -07:00
if ( ! deleteModal ) {
dispatch ( deleteStatus ( status . get ( 'id' ) , history , withRedraft ) ) ;
} else {
dispatch ( openModal ( 'CONFIRM' , {
2021-12-30 08:38:57 -08:00
icon : withRedraft ? require ( '@tabler/icons/icons/edit.svg' ) : require ( '@tabler/icons/icons/trash.svg' ) ,
heading : intl . formatMessage ( withRedraft ? messages . redraftHeading : messages . deleteHeading ) ,
2020-04-21 12:41:13 -07:00
message : intl . formatMessage ( withRedraft ? messages . redraftMessage : messages . deleteMessage ) ,
confirm : intl . formatMessage ( withRedraft ? messages . redraftConfirm : messages . deleteConfirm ) ,
onConfirm : ( ) => dispatch ( deleteStatus ( status . get ( 'id' ) , history , withRedraft ) ) ,
} ) ) ;
}
} ) ;
2020-03-27 13:59:38 -07:00
}
handleDirectClick = ( account , router ) => {
this . props . dispatch ( directCompose ( account , router ) ) ;
}
2021-10-13 11:55:02 -07:00
handleChatClick = ( account , router ) => {
2021-10-14 10:23:51 -07:00
this . props . dispatch ( launchChat ( account . get ( 'id' ) , router ) ) ;
2021-10-13 11:55:02 -07:00
}
2020-03-27 13:59:38 -07:00
handleMentionClick = ( account , router ) => {
this . props . dispatch ( mentionCompose ( account , router ) ) ;
}
handleOpenMedia = ( media , index ) => {
this . props . dispatch ( openModal ( 'MEDIA' , { media , index } ) ) ;
}
handleOpenVideo = ( media , time ) => {
this . props . dispatch ( openModal ( 'VIDEO' , { media , time } ) ) ;
}
2021-08-28 05:17:14 -07:00
handleHotkeyOpenMedia = e => {
const { onOpenMedia , onOpenVideo } = this . props ;
const status = this . _properStatus ( ) ;
e . preventDefault ( ) ;
if ( status . get ( 'media_attachments' ) . size > 0 ) {
if ( status . getIn ( [ 'media_attachments' , 0 , 'type' ] ) === 'video' ) {
onOpenVideo ( status . getIn ( [ 'media_attachments' , 0 ] ) , 0 ) ;
} else {
onOpenMedia ( status . get ( 'media_attachments' ) , 0 ) ;
}
}
}
2020-03-27 13:59:38 -07:00
handleMuteClick = ( account ) => {
this . props . dispatch ( initMuteModal ( account ) ) ;
}
handleConversationMuteClick = ( status ) => {
if ( status . get ( 'muted' ) ) {
this . props . dispatch ( unmuteStatus ( status . get ( 'id' ) ) ) ;
} else {
this . props . dispatch ( muteStatus ( status . get ( 'id' ) ) ) ;
}
}
handleToggleHidden = ( status ) => {
if ( status . get ( 'hidden' ) ) {
this . props . dispatch ( revealStatus ( status . get ( 'id' ) ) ) ;
} else {
this . props . dispatch ( hideStatus ( status . get ( 'id' ) ) ) ;
}
}
handleToggleAll = ( ) => {
const { status , ancestorsIds , descendantsIds } = this . props ;
const statusIds = [ status . get ( 'id' ) ] . concat ( ancestorsIds . toJS ( ) , descendantsIds . toJS ( ) ) ;
if ( status . get ( 'hidden' ) ) {
this . props . dispatch ( revealStatus ( statusIds ) ) ;
} else {
this . props . dispatch ( hideStatus ( statusIds ) ) ;
}
}
handleBlockClick = ( status ) => {
const { dispatch , intl } = this . props ;
const account = status . get ( 'account' ) ;
dispatch ( openModal ( 'CONFIRM' , {
2021-12-30 08:38:57 -08:00
icon : require ( '@tabler/icons/icons/ban.svg' ) ,
heading : < FormattedMessage id = 'confirmations.block.heading' defaultMessage = 'Block @{name}' values = { { name : account . get ( 'acct' ) } } / > ,
2020-03-27 13:59:38 -07:00
message : < FormattedMessage id = 'confirmations.block.message' defaultMessage = 'Are you sure you want to block {name}?' values = { { name : < strong > @ { account . get ( 'acct' ) } < /strong> }} / > ,
confirm : intl . formatMessage ( messages . blockConfirm ) ,
onConfirm : ( ) => dispatch ( blockAccount ( account . get ( 'id' ) ) ) ,
secondary : intl . formatMessage ( messages . blockAndReport ) ,
onSecondary : ( ) => {
dispatch ( blockAccount ( account . get ( 'id' ) ) ) ;
dispatch ( initReport ( account , status ) ) ;
} ,
} ) ) ;
}
handleReport = ( status ) => {
this . props . dispatch ( initReport ( status . get ( 'account' ) , status ) ) ;
}
handleEmbed = ( status ) => {
this . props . dispatch ( openModal ( 'EMBED' , { url : status . get ( 'url' ) } ) ) ;
}
2021-01-18 13:27:35 -08:00
handleDeactivateUser = ( status ) => {
const { dispatch , intl } = this . props ;
dispatch ( deactivateUserModal ( intl , status . getIn ( [ 'account' , 'id' ] ) ) ) ;
}
handleDeleteUser = ( status ) => {
const { dispatch , intl } = this . props ;
dispatch ( deleteUserModal ( intl , status . getIn ( [ 'account' , 'id' ] ) ) ) ;
}
2021-01-18 18:59:07 -08:00
handleToggleStatusSensitivity = ( status ) => {
const { dispatch , intl } = this . props ;
dispatch ( toggleStatusSensitivityModal ( intl , status . get ( 'id' ) , status . get ( 'sensitive' ) ) ) ;
}
2021-01-18 13:57:20 -08:00
handleDeleteStatus = ( status ) => {
const { dispatch , intl } = this . props ;
dispatch ( deleteStatusModal ( intl , status . get ( 'id' ) ) ) ;
}
2020-03-27 13:59:38 -07:00
handleHotkeyMoveUp = ( ) => {
this . handleMoveUp ( this . props . status . get ( 'id' ) ) ;
}
handleHotkeyMoveDown = ( ) => {
this . handleMoveDown ( this . props . status . get ( 'id' ) ) ;
}
handleHotkeyReply = e => {
e . preventDefault ( ) ;
this . handleReplyClick ( this . props . status ) ;
}
handleHotkeyFavourite = ( ) => {
this . handleFavouriteClick ( this . props . status ) ;
}
handleHotkeyBoost = ( ) => {
this . handleReblogClick ( this . props . status ) ;
}
handleHotkeyMention = e => {
e . preventDefault ( ) ;
this . handleMentionClick ( this . props . status . get ( 'account' ) ) ;
}
handleHotkeyOpenProfile = ( ) => {
this . context . router . history . push ( ` /@ ${ this . props . status . getIn ( [ 'account' , 'acct' ] ) } ` ) ;
}
handleHotkeyToggleHidden = ( ) => {
this . handleToggleHidden ( this . props . status ) ;
}
handleHotkeyToggleSensitive = ( ) => {
this . handleToggleMediaVisibility ( ) ;
}
2021-07-21 04:58:22 -07:00
handleHotkeyReact = ( ) => {
this . _expandEmojiSelector ( ) ;
}
2020-03-27 13:59:38 -07:00
handleMoveUp = id => {
const { status , ancestorsIds , descendantsIds } = this . props ;
if ( id === status . get ( 'id' ) ) {
this . _selectChild ( ancestorsIds . size - 1 , true ) ;
} else {
let index = ancestorsIds . indexOf ( id ) ;
if ( index === - 1 ) {
index = descendantsIds . indexOf ( id ) ;
this . _selectChild ( ancestorsIds . size + index , true ) ;
} else {
this . _selectChild ( index - 1 , true ) ;
}
}
}
handleMoveDown = id => {
const { status , ancestorsIds , descendantsIds } = this . props ;
if ( id === status . get ( 'id' ) ) {
this . _selectChild ( ancestorsIds . size + 1 , false ) ;
} else {
let index = ancestorsIds . indexOf ( id ) ;
if ( index === - 1 ) {
index = descendantsIds . indexOf ( id ) ;
this . _selectChild ( ancestorsIds . size + index + 2 , false ) ;
} else {
this . _selectChild ( index + 1 , false ) ;
}
}
}
2021-07-21 04:58:22 -07:00
handleEmojiSelectorExpand = e => {
if ( e . key === 'Enter' ) {
this . _expandEmojiSelector ( ) ;
}
e . preventDefault ( ) ;
}
handleEmojiSelectorUnfocus = ( ) => {
this . setState ( { emojiSelectorFocused : false } ) ;
}
_expandEmojiSelector = ( ) => {
this . setState ( { emojiSelectorFocused : true } ) ;
const firstEmoji = this . status . querySelector ( '.emoji-react-selector .emoji-react-selector__emoji' ) ;
firstEmoji . focus ( ) ;
} ;
2020-04-14 14:47:35 -07:00
_selectChild ( index , align _top ) {
2020-03-27 13:59:38 -07:00
const container = this . node ;
const element = container . querySelectorAll ( '.focusable' ) [ index ] ;
if ( element ) {
if ( align _top && container . scrollTop > element . offsetTop ) {
element . scrollIntoView ( true ) ;
} else if ( ! align _top && container . scrollTop + container . clientHeight < element . offsetTop + element . offsetHeight ) {
element . scrollIntoView ( false ) ;
}
element . focus ( ) ;
}
}
2021-04-21 12:47:39 -07:00
renderTombstone ( id ) {
return (
2021-04-21 13:25:18 -07:00
< div className = 'tombstone' key = { id } >
2021-04-21 13:37:30 -07:00
< p > < FormattedMessage id = 'statuses.tombstone' defaultMessage = 'One or more posts is unavailable.' / > < / p >
2021-04-21 12:47:39 -07:00
< / d i v >
) ;
}
renderStatus ( id ) {
2021-10-06 15:50:43 -07:00
const { status } = this . props ;
2021-04-21 12:47:39 -07:00
return (
2021-10-06 15:50:43 -07:00
< ThreadStatus
2020-03-27 13:59:38 -07:00
key = { id }
id = { id }
2021-10-06 15:50:43 -07:00
focusedStatusId = { status && status . get ( 'id' ) }
2020-03-27 13:59:38 -07:00
onMoveUp = { this . handleMoveUp }
onMoveDown = { this . handleMoveDown }
contextType = 'thread'
/ >
2021-04-21 12:47:39 -07:00
) ;
}
2021-10-09 15:47:25 -07:00
renderPendingStatus ( id ) {
2021-10-09 19:16:37 -07:00
const idempotencyKey = id . replace ( /^末pending-/ , '' ) ;
2021-10-09 15:47:25 -07:00
return (
< PendingStatus
2021-10-09 19:39:15 -07:00
className = 'thread__status'
2021-10-09 15:47:25 -07:00
key = { id }
idempotencyKey = { idempotencyKey }
focusedStatusId = { status && status . get ( 'id' ) }
onMoveUp = { this . handleMoveUp }
onMoveDown = { this . handleMoveDown }
contextType = 'thread'
/ >
) ;
}
2021-04-21 12:47:39 -07:00
renderChildren ( list ) {
return list . map ( id => {
2021-04-21 16:28:43 -07:00
if ( id . endsWith ( '-tombstone' ) ) {
2021-04-21 12:47:39 -07:00
return this . renderTombstone ( id ) ;
2021-10-09 19:16:37 -07:00
} else if ( id . startsWith ( '末pending-' ) ) {
2021-10-09 15:47:25 -07:00
return this . renderPendingStatus ( id ) ;
2021-04-21 12:47:39 -07:00
} else {
return this . renderStatus ( id ) ;
}
} ) ;
2020-03-27 13:59:38 -07:00
}
setRef = c => {
this . node = c ;
}
2021-07-21 04:58:22 -07:00
setStatusRef = c => {
this . status = c ;
}
2020-07-04 16:41:41 -07:00
componentDidUpdate ( prevProps , prevState ) {
const { params , status } = this . props ;
const { ancestorsIds } = prevProps ;
2021-11-04 11:16:28 -07:00
if ( params . statusId !== prevProps . params . statusId ) {
2020-06-24 14:02:14 -07:00
this . _scrolledIntoView = false ;
2021-11-04 11:16:28 -07:00
this . fetchData ( ) ;
2020-06-24 14:02:14 -07:00
}
2020-07-04 16:41:41 -07:00
if ( status && status . get ( 'id' ) !== prevState . loadedStatusId ) {
this . setState ( { showMedia : defaultMediaVisibility ( status ) , loadedStatusId : status . get ( 'id' ) } ) ;
2020-06-24 14:02:14 -07:00
}
2020-03-27 13:59:38 -07:00
if ( this . _scrolledIntoView ) {
return ;
}
2021-07-11 15:50:30 -07:00
if ( prevProps . status && ancestorsIds && ancestorsIds . size > 0 && this . node ) {
2020-09-09 13:03:26 -07:00
const element = this . node . querySelector ( '.detailed-status' ) ;
2020-03-27 13:59:38 -07:00
window . requestAnimationFrame ( ( ) => {
element . scrollIntoView ( true ) ;
} ) ;
this . _scrolledIntoView = true ;
}
}
2020-04-14 14:47:35 -07:00
componentWillUnmount ( ) {
2020-03-27 13:59:38 -07:00
detachFullscreenListener ( this . onFullScreenChange ) ;
}
onFullScreenChange = ( ) => {
this . setState ( { fullscreen : isFullscreen ( ) } ) ;
}
2021-11-04 10:34:22 -07:00
handleRefresh = ( ) => {
2021-11-04 11:16:28 -07:00
return this . fetchData ( ) ;
2021-11-04 10:34:22 -07:00
}
2020-04-14 14:47:35 -07:00
render ( ) {
2020-03-27 13:59:38 -07:00
let ancestors , descendants ;
2021-09-12 17:36:18 -07:00
const { status , ancestorsIds , descendantsIds , intl , domain } = this . props ;
2020-03-27 13:59:38 -07:00
if ( status === null ) {
return (
< Column >
< MissingIndicator / >
< / C o l u m n >
) ;
}
if ( ancestorsIds && ancestorsIds . size > 0 ) {
2021-09-27 13:03:20 -07:00
ancestors = this . renderChildren ( ancestorsIds ) ;
2020-03-27 13:59:38 -07:00
}
if ( descendantsIds && descendantsIds . size > 0 ) {
2021-09-27 13:03:20 -07:00
descendants = this . renderChildren ( descendantsIds ) ;
2020-03-27 13:59:38 -07:00
}
const handlers = {
moveUp : this . handleHotkeyMoveUp ,
moveDown : this . handleHotkeyMoveDown ,
reply : this . handleHotkeyReply ,
favourite : this . handleHotkeyFavourite ,
boost : this . handleHotkeyBoost ,
mention : this . handleHotkeyMention ,
openProfile : this . handleHotkeyOpenProfile ,
toggleHidden : this . handleHotkeyToggleHidden ,
toggleSensitive : this . handleHotkeyToggleSensitive ,
2021-08-28 05:17:14 -07:00
openMedia : this . handleHotkeyOpenMedia ,
2021-07-21 04:58:22 -07:00
react : this . handleHotkeyReact ,
2020-03-27 13:59:38 -07:00
} ;
2021-10-14 06:49:33 -07:00
const titleMessage = status && status . get ( 'visibility' ) === 'direct' ? messages . titleDirect : messages . title ;
2020-03-27 13:59:38 -07:00
return (
2021-10-09 09:42:31 -07:00
< Column label = { intl . formatMessage ( messages . detailedStatus ) } transparent >
2021-10-14 06:49:33 -07:00
< SubNavigation message = { intl . formatMessage ( titleMessage ) } / >
2021-09-12 17:36:18 -07:00
{ / *
Eye icon to show / hide all CWs in a thread .
I ' m not convinced of the value of this . It needs a better design at the very least .
* / }
{ / * m e & &
2020-03-27 13:59:38 -07:00
< ColumnHeader
extraButton = { (
< button
className = 'column-header__button'
title = { intl . formatMessage ( status . get ( 'hidden' ) ? messages . revealAll : messages . hideAll ) }
aria - label = { intl . formatMessage ( status . get ( 'hidden' ) ? messages . revealAll : messages . hideAll ) }
onClick = { this . handleToggleAll }
aria - pressed = {
2020-04-14 11:44:40 -07:00
status . get ( 'hidden' ) ? 'false' : 'true' }
>
< Icon id = { status . get ( 'hidden' ) ? 'eye-slash' : 'eye'
2020-03-27 13:59:38 -07:00
}
2020-04-14 11:44:40 -07:00
/ >
2020-03-27 13:59:38 -07:00
< / b u t t o n >
) }
/ >
2021-09-12 17:36:18 -07:00
* / }
2020-03-27 13:59:38 -07:00
2021-10-06 11:59:52 -07:00
< div ref = { this . setRef } className = 'thread' >
2021-11-04 10:34:22 -07:00
< PullToRefresh onRefresh = { this . handleRefresh } >
2021-11-03 18:35:40 -07:00
{ ancestors && (
< div className = 'thread__ancestors' > { ancestors } < / d i v >
) }
2020-03-27 13:59:38 -07:00
2021-11-03 18:35:40 -07:00
< div className = 'thread__status thread__status--focused' >
< HotKeys handlers = { handlers } >
< div ref = { this . setStatusRef } className = { classNames ( 'focusable' , 'detailed-status__wrapper' ) } tabIndex = '0' aria - label = { textForScreenReader ( intl , status , false ) } >
< DetailedStatus
status = { status }
onOpenVideo = { this . handleOpenVideo }
onOpenMedia = { this . handleOpenMedia }
onToggleHidden = { this . handleToggleHidden }
domain = { domain }
showMedia = { this . state . showMedia }
onToggleMediaVisibility = { this . handleToggleMediaVisibility }
/ >
< ActionBar
status = { status }
onReply = { this . handleReplyClick }
onFavourite = { this . handleFavouriteClick }
onEmojiReact = { this . handleEmojiReactClick }
onReblog = { this . handleReblogClick }
onDelete = { this . handleDeleteClick }
onDirect = { this . handleDirectClick }
onChat = { this . handleChatClick }
onMention = { this . handleMentionClick }
onMute = { this . handleMuteClick }
onMuteConversation = { this . handleConversationMuteClick }
onBlock = { this . handleBlockClick }
onReport = { this . handleReport }
onPin = { this . handlePin }
onBookmark = { this . handleBookmark }
onEmbed = { this . handleEmbed }
onDeactivateUser = { this . handleDeactivateUser }
onDeleteUser = { this . handleDeleteUser }
onToggleStatusSensitivity = { this . handleToggleStatusSensitivity }
onDeleteStatus = { this . handleDeleteStatus }
allowedEmoji = { this . props . allowedEmoji }
emojiSelectorFocused = { this . state . emojiSelectorFocused }
handleEmojiSelectorExpand = { this . handleEmojiSelectorExpand }
handleEmojiSelectorUnfocus = { this . handleEmojiSelectorUnfocus }
/ >
< / d i v >
< / H o t K e y s >
< / d i v >
{ descendants && (
< div className = 'thread__descendants' > { descendants } < / d i v >
) }
2021-11-04 10:34:22 -07:00
< / P u l l T o R e f r e s h >
2020-03-27 13:59:38 -07:00
< / d i v >
< / C o l u m n >
) ;
}
}