2017-04-22 03:05:35 +09:00
import PropTypes from 'prop-types' ;
2023-05-28 23:38:10 +09:00
import { defineMessages , injectIntl } from 'react-intl' ;
2017-11-07 22:24:55 +09:00
import classNames from 'classnames' ;
2023-05-28 23:38:10 +09:00
import { Helmet } from 'react-helmet' ;
2023-10-20 02:44:55 +09:00
import { withRouter } from 'react-router-dom' ;
2023-05-28 23:38:10 +09:00
2024-01-08 19:57:40 +09:00
import { createSelector } from '@reduxjs/toolkit' ;
2023-05-28 23:38:10 +09:00
import Immutable from 'immutable' ;
2017-02-18 10:37:59 +09:00
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2023-05-28 23:38:10 +09:00
import ImmutablePureComponent from 'react-immutable-pure-component' ;
import { connect } from 'react-redux' ;
import { HotKeys } from 'react-hotkeys' ;
2024-01-16 19:27:26 +09:00
import ChatIcon from '@/material-icons/400-24px/chat.svg?react' ;
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react' ;
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react' ;
2023-11-15 20:01:51 +09:00
import { Icon } from 'flavours/glitch/components/icon' ;
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator' ;
import ScrollContainer from 'flavours/glitch/containers/scroll_container' ;
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error' ;
2024-05-20 02:07:32 +09:00
import { identityContextPropShape , withIdentity } from 'flavours/glitch/identity_context' ;
2023-11-15 20:01:51 +09:00
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning' ;
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router' ;
import { initBlockModal } from '../../actions/blocks' ;
2023-02-14 04:09:01 +09:00
import {
2023-05-28 23:38:10 +09:00
replyCompose ,
mentionCompose ,
directCompose ,
2023-11-15 20:01:51 +09:00
} from '../../actions/compose' ;
2017-02-18 10:37:59 +09:00
import {
favourite ,
unfavourite ,
2018-04-12 02:42:25 +09:00
bookmark ,
unbookmark ,
2017-02-18 10:37:59 +09:00
reblog ,
2017-05-21 00:31:47 +09:00
unreblog ,
2017-08-25 08:41:18 +09:00
pin ,
unpin ,
2023-11-15 20:01:51 +09:00
} from '../../actions/interactions' ;
import { changeLocalSetting } from '../../actions/local_settings' ;
import { openModal } from '../../actions/modal' ;
import { initMuteModal } from '../../actions/mutes' ;
import { initReport } from '../../actions/reports' ;
2023-05-28 23:38:10 +09:00
import {
fetchStatus ,
muteStatus ,
unmuteStatus ,
deleteStatus ,
editStatus ,
hideStatus ,
revealStatus ,
translateStatus ,
undoStatusTranslation ,
2023-11-15 20:01:51 +09:00
} from '../../actions/statuses' ;
2023-05-28 23:38:10 +09:00
import ColumnHeader from '../../components/column_header' ;
2023-11-15 20:01:51 +09:00
import { textForScreenReader , defaultMediaVisibility } from '../../components/status' ;
import StatusContainer from '../../containers/status_container' ;
import { boostModal , favouriteModal , deleteModal } from '../../initial_state' ;
import { makeGetStatus , makeGetPictureInPicture } from '../../selectors' ;
import Column from '../ui/components/column' ;
2023-05-28 23:38:10 +09:00
import { attachFullscreenListener , detachFullscreenListener , isFullscreen } from '../ui/util/fullscreen' ;
import ActionBar from './components/action_bar' ;
import DetailedStatus from './components/detailed_status' ;
2017-04-23 11:39:50 +09:00
2023-10-20 02:44:55 +09:00
2017-04-23 11:39:50 +09:00
const messages = defineMessages ( {
deleteConfirm : { id : 'confirmations.delete.confirm' , defaultMessage : 'Delete' } ,
2017-05-21 00:31:47 +09:00
deleteMessage : { id : 'confirmations.delete.message' , defaultMessage : 'Are you sure you want to delete this status?' } ,
2018-06-16 04:29:04 +09:00
redraftConfirm : { id : 'confirmations.redraft.confirm' , defaultMessage : 'Delete & redraft' } ,
2023-07-22 02:09:13 +09:00
redraftMessage : { id : 'confirmations.redraft.message' , defaultMessage : 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' } ,
2018-03-29 02:56:46 +09:00
revealAll : { id : 'status.show_more_all' , defaultMessage : 'Show more for all' } ,
hideAll : { id : 'status.show_less_all' , defaultMessage : 'Show less for all' } ,
2023-04-24 15:07:19 +09:00
statusTitleWithAttachments : { id : 'status.title.with_attachments' , defaultMessage : '{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}' } ,
2018-08-28 19:01:04 +09:00
detailedStatus : { id : 'status.detailed_status' , defaultMessage : 'Detailed conversation view' } ,
2018-10-06 00:46:35 +09: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-12-20 20:37:14 +09:00
tootHeading : { id : 'account.posts_with_replies' , defaultMessage : 'Posts and replies' } ,
2017-04-23 11:39:50 +09:00
} ) ;
2016-09-16 07:21:51 +09:00
2016-10-25 00:11:02 +09:00
const makeMapStateToProps = ( ) => {
const getStatus = makeGetStatus ( ) ;
2023-01-05 21:32:29 +09:00
const getPictureInPicture = makeGetPictureInPicture ( ) ;
2016-10-25 00:11:02 +09:00
2019-07-02 23:03:54 +09:00
const getAncestorsIds = createSelector ( [
( _ , { id } ) => id ,
state => state . getIn ( [ 'contexts' , 'inReplyTos' ] ) ,
] , ( statusId , inReplyTos ) => {
2018-11-30 20:17:56 +09:00
let ancestorsIds = Immutable . List ( ) ;
2019-07-02 23:03:54 +09:00
ancestorsIds = ancestorsIds . withMutations ( mutable => {
let id = statusId ;
2021-11-26 07:46:39 +09:00
while ( id && ! mutable . includes ( id ) ) {
2019-07-02 23:03:54 +09:00
mutable . unshift ( id ) ;
id = inReplyTos . get ( id ) ;
}
} ) ;
return ancestorsIds ;
} ) ;
const getDescendantsIds = createSelector ( [
( _ , { id } ) => id ,
state => state . getIn ( [ 'contexts' , 'replies' ] ) ,
2019-08-30 08:57:38 +09:00
state => state . get ( 'statuses' ) ,
] , ( statusId , contextReplies , statuses ) => {
let descendantsIds = [ ] ;
const ids = [ statusId ] ;
2018-11-30 20:17:56 +09:00
2019-08-30 08:57:38 +09:00
while ( ids . length > 0 ) {
2021-11-26 07:46:39 +09:00
let id = ids . pop ( ) ;
2019-08-30 08:57:38 +09:00
const replies = contextReplies . get ( id ) ;
2018-11-30 20:17:56 +09:00
2019-08-30 08:57:38 +09:00
if ( statusId !== id ) {
descendantsIds . push ( id ) ;
}
2018-11-30 20:17:56 +09:00
2019-08-30 08:57:38 +09:00
if ( replies ) {
replies . reverse ( ) . forEach ( reply => {
2021-11-26 07:46:39 +09:00
if ( ! ids . includes ( reply ) && ! descendantsIds . includes ( reply ) && statusId !== reply ) ids . push ( reply ) ;
2019-08-30 08:57:38 +09:00
} ) ;
2019-07-02 23:03:54 +09:00
}
2019-08-30 08:57:38 +09:00
}
let insertAt = descendantsIds . findIndex ( ( id ) => statuses . get ( id ) . get ( 'in_reply_to_account_id' ) !== statuses . get ( id ) . get ( 'account' ) ) ;
if ( insertAt !== - 1 ) {
descendantsIds . forEach ( ( id , idx ) => {
if ( idx > insertAt && statuses . get ( id ) . get ( 'in_reply_to_account_id' ) === statuses . get ( id ) . get ( 'account' ) ) {
descendantsIds . splice ( idx , 1 ) ;
descendantsIds . splice ( insertAt , 0 , id ) ;
insertAt += 1 ;
}
} ) ;
}
2018-11-30 20:24:12 +09:00
2019-08-30 08:57:38 +09:00
return Immutable . List ( descendantsIds ) ;
2019-07-02 23:03:54 +09:00
} ) ;
2018-11-30 20:17:56 +09:00
2019-07-02 23:03:54 +09:00
const mapStateToProps = ( state , props ) => {
const status = getStatus ( state , { id : props . params . statusId } ) ;
2023-01-05 21:32:29 +09:00
let ancestorsIds = Immutable . List ( ) ;
2019-07-02 23:03:54 +09:00
let descendantsIds = Immutable . List ( ) ;
2018-11-30 20:24:58 +09:00
2019-07-02 23:03:54 +09:00
if ( status ) {
2023-01-05 21:32:29 +09:00
ancestorsIds = getAncestorsIds ( state , { id : status . get ( 'in_reply_to_id' ) } ) ;
2019-07-02 23:03:54 +09:00
descendantsIds = getDescendantsIds ( state , { id : status . get ( 'id' ) } ) ;
2018-11-30 20:17:56 +09:00
}
return {
2022-10-20 21:35:29 +09:00
isLoading : state . getIn ( [ 'statuses' , props . params . statusId , 'isLoading' ] ) ,
2018-11-30 20:17:56 +09:00
status ,
ancestorsIds ,
descendantsIds ,
settings : state . get ( 'local_settings' ) ,
2018-11-28 02:25:51 +09:00
askReplyConfirmation : state . getIn ( [ 'local_settings' , 'confirm_before_clearing_draft' ] ) && state . getIn ( [ 'compose' , 'text' ] ) . trim ( ) . length !== 0 ,
2019-01-20 19:47:17 +09:00
domain : state . getIn ( [ 'meta' , 'domain' ] ) ,
2023-01-05 21:32:29 +09:00
pictureInPicture : getPictureInPicture ( state , { id : props . params . statusId } ) ,
2018-11-30 20:17:56 +09:00
} ;
} ;
2016-10-25 00:11:02 +09:00
return mapStateToProps ;
} ;
2016-09-16 07:21:51 +09:00
2022-09-29 11:39:33 +09:00
const truncate = ( str , num ) => {
2023-05-29 18:55:16 +09:00
const arr = Array . from ( str ) ;
if ( arr . length > num ) {
return arr . slice ( 0 , num ) . join ( '' ) + '…' ;
2022-09-29 11:39:33 +09:00
} else {
return str ;
}
} ;
2023-04-15 00:29:09 +09:00
const titleFromStatus = ( intl , status ) => {
2022-09-29 11:39:33 +09:00
const displayName = status . getIn ( [ 'account' , 'display_name' ] ) ;
const username = status . getIn ( [ 'account' , 'username' ] ) ;
2023-04-15 00:29:09 +09:00
const user = displayName . trim ( ) . length === 0 ? username : displayName ;
2022-09-29 11:39:33 +09:00
const text = status . get ( 'search_index' ) ;
2023-04-15 00:29:09 +09:00
const attachmentCount = status . get ( 'media_attachments' ) . size ;
2022-09-29 11:39:33 +09:00
2023-04-15 00:29:09 +09:00
return text ? ` ${ user } : " ${ truncate ( text , 30 ) } " ` : intl . formatMessage ( messages . statusTitleWithAttachments , { user , attachmentCount } ) ;
2022-09-29 11:39:33 +09:00
} ;
2019-09-09 22:16:08 +09:00
class Status extends ImmutablePureComponent {
2017-05-12 21:44:10 +09:00
static propTypes = {
2024-05-20 02:07:32 +09:00
identity : identityContextPropShape ,
2017-05-12 21:44:10 +09:00
params : PropTypes . object . isRequired ,
dispatch : PropTypes . func . isRequired ,
status : ImmutablePropTypes . map ,
2022-10-20 21:35:29 +09:00
isLoading : PropTypes . bool ,
2017-06-30 18:15:18 +09:00
settings : ImmutablePropTypes . map . isRequired ,
2023-07-21 20:14:30 +09:00
ancestorsIds : ImmutablePropTypes . list . isRequired ,
descendantsIds : ImmutablePropTypes . list . isRequired ,
2017-05-21 00:31:47 +09:00
intl : PropTypes . object . isRequired ,
2018-10-06 00:46:35 +09:00
askReplyConfirmation : PropTypes . bool ,
2019-08-01 19:26:58 +09:00
multiColumn : PropTypes . bool ,
2019-01-20 19:47:17 +09:00
domain : PropTypes . string . isRequired ,
2023-01-05 21:32:29 +09:00
pictureInPicture : ImmutablePropTypes . contains ( {
inUse : PropTypes . bool ,
available : PropTypes . bool ,
} ) ,
2023-10-20 02:44:55 +09:00
... WithRouterPropTypes
2017-05-12 21:44:10 +09:00
} ;
2016-09-16 07:21:51 +09:00
2017-11-07 22:24:55 +09:00
state = {
fullscreen : false ,
2018-08-29 00:16:30 +09:00
isExpanded : undefined ,
2018-08-28 21:10:26 +09:00
threadExpanded : undefined ,
2018-11-29 04:20:03 +09:00
statusId : undefined ,
2019-05-27 01:58:14 +09:00
loadedStatusId : undefined ,
showMedia : undefined ,
revealBehindCW : undefined ,
2017-11-07 22:24:55 +09:00
} ;
componentDidMount ( ) {
attachFullscreenListener ( this . onFullScreenChange ) ;
2018-11-29 04:20:03 +09:00
this . props . dispatch ( fetchStatus ( this . props . params . statusId ) ) ;
2023-10-09 23:46:09 +09:00
this . _scrollStatusIntoView ( ) ;
2017-11-07 22:24:55 +09:00
}
2018-11-29 04:20:03 +09:00
static getDerivedStateFromProps ( props , state ) {
2019-05-27 01:58:14 +09:00
let update = { } ;
let updated = false ;
if ( props . params . statusId && state . statusId !== props . params . statusId ) {
props . dispatch ( fetchStatus ( props . params . statusId ) ) ;
update . threadExpanded = undefined ;
update . statusId = props . params . statusId ;
updated = true ;
2016-09-16 07:21:51 +09:00
}
2018-11-29 04:20:03 +09:00
2019-05-27 01:58:14 +09:00
const revealBehindCW = props . settings . getIn ( [ 'media' , 'reveal_behind_cw' ] ) ;
if ( revealBehindCW !== state . revealBehindCW ) {
update . revealBehindCW = revealBehindCW ;
if ( revealBehindCW ) update . showMedia = defaultMediaVisibility ( props . status , props . settings ) ;
updated = true ;
}
2018-11-29 04:20:03 +09:00
2019-05-27 01:58:14 +09:00
if ( props . status && state . loadedStatusId !== props . status . get ( 'id' ) ) {
update . showMedia = defaultMediaVisibility ( props . status , props . settings ) ;
update . loadedStatusId = props . status . get ( 'id' ) ;
update . isExpanded = autoUnfoldCW ( props . settings , props . status ) ;
updated = true ;
}
return updated ? update : null ;
2017-04-22 03:05:35 +09:00
}
2016-09-16 07:21:51 +09:00
2022-07-25 03:01:30 +09:00
handleToggleHidden = ( ) => {
const { status } = this . props ;
if ( this . props . settings . getIn ( [ 'content_warnings' , 'shared_state' ] ) ) {
if ( status . get ( 'hidden' ) ) {
this . props . dispatch ( revealStatus ( status . get ( 'id' ) ) ) ;
} else {
this . props . dispatch ( hideStatus ( status . get ( 'id' ) ) ) ;
}
} else if ( this . props . status . get ( 'spoiler_text' ) ) {
2018-03-28 22:40:34 +09:00
this . setExpansion ( ! this . state . isExpanded ) ;
2017-11-28 07:05:03 +09:00
}
2023-02-04 04:52:07 +09:00
} ;
2017-11-28 07:05:03 +09:00
2019-05-27 01:58:14 +09:00
handleToggleMediaVisibility = ( ) => {
this . setState ( { showMedia : ! this . state . showMedia } ) ;
2023-02-04 04:52:07 +09:00
} ;
2019-05-27 01:58:14 +09:00
2017-12-10 01:26:22 +09:00
handleModalFavourite = ( status ) => {
this . props . dispatch ( favourite ( status ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-12-10 01:26:22 +09:00
handleFavouriteClick = ( status , e ) => {
2022-10-07 17:14:31 +09:00
const { dispatch } = this . props ;
2024-05-20 02:07:32 +09:00
const { signedIn } = this . props . identity ;
2022-10-07 17:14:31 +09:00
if ( signedIn ) {
if ( status . get ( 'favourited' ) ) {
dispatch ( unfavourite ( status ) ) ;
2017-12-10 01:26:22 +09:00
} else {
2022-10-07 17:14:31 +09:00
if ( ( e && e . shiftKey ) || ! favouriteModal ) {
this . handleModalFavourite ( status ) ;
} else {
2023-05-25 22:42:37 +09:00
dispatch ( openModal ( {
modalType : 'FAVOURITE' ,
modalProps : {
status ,
onFavourite : this . handleModalFavourite ,
} ,
} ) ) ;
2022-10-07 17:14:31 +09:00
}
2017-12-10 01:26:22 +09:00
}
2022-10-07 17:14:31 +09:00
} else {
2023-05-25 22:42:37 +09:00
dispatch ( openModal ( {
modalType : 'INTERACTION' ,
modalProps : {
type : 'favourite' ,
accountId : status . getIn ( [ 'account' , 'id' ] ) ,
2023-07-27 23:11:17 +09:00
url : status . get ( 'uri' ) ,
2023-05-25 22:42:37 +09:00
} ,
2022-10-07 17:14:31 +09:00
} ) ) ;
2017-02-17 10:33:10 +09:00
}
2023-02-04 04:52:07 +09:00
} ;
2016-09-18 00:47:26 +09:00
2017-08-25 08:41:18 +09:00
handlePin = ( status ) => {
if ( status . get ( 'pinned' ) ) {
this . props . dispatch ( unpin ( status ) ) ;
} else {
this . props . dispatch ( pin ( status ) ) ;
}
2023-02-04 04:52:07 +09:00
} ;
2017-08-25 08:41:18 +09:00
2017-05-12 21:44:10 +09:00
handleReplyClick = ( status ) => {
2022-10-07 17:14:31 +09:00
const { askReplyConfirmation , dispatch , intl } = this . props ;
2024-05-20 02:07:32 +09:00
const { signedIn } = this . props . identity ;
2022-10-07 17:14:31 +09:00
if ( signedIn ) {
if ( askReplyConfirmation ) {
2023-05-25 22:42:37 +09:00
dispatch ( openModal ( {
modalType : 'CONFIRM' ,
modalProps : {
message : intl . formatMessage ( messages . replyMessage ) ,
confirm : intl . formatMessage ( messages . replyConfirm ) ,
onDoNotAsk : ( ) => dispatch ( changeLocalSetting ( [ 'confirm_before_clearing_draft' ] , false ) ) ,
2023-10-20 02:44:55 +09:00
onConfirm : ( ) => dispatch ( replyCompose ( status , this . props . history ) ) ,
2023-05-25 22:42:37 +09:00
} ,
2022-10-07 17:14:31 +09:00
} ) ) ;
} else {
2023-10-20 02:44:55 +09:00
dispatch ( replyCompose ( status , this . props . history ) ) ;
2022-10-07 17:14:31 +09:00
}
2018-10-06 00:46:35 +09:00
} else {
2023-05-25 22:42:37 +09:00
dispatch ( openModal ( {
modalType : 'INTERACTION' ,
modalProps : {
type : 'reply' ,
accountId : status . getIn ( [ 'account' , 'id' ] ) ,
2023-07-27 23:11:17 +09:00
url : status . get ( 'uri' ) ,
2023-05-25 22:42:37 +09:00
} ,
2022-10-07 17:14:31 +09:00
} ) ) ;
2018-10-06 00:46:35 +09:00
}
2023-02-04 04:52:07 +09:00
} ;
2016-09-18 00:47:26 +09:00
2021-02-11 08:53:12 +09:00
handleModalReblog = ( status , privacy ) => {
2019-06-12 16:46:39 +09:00
const { dispatch } = this . props ;
if ( status . get ( 'reblogged' ) ) {
2024-05-24 03:22:42 +09:00
dispatch ( unreblog ( { statusId : status . get ( 'id' ) } ) ) ;
2019-06-12 16:46:39 +09:00
} else {
2024-05-24 03:22:42 +09:00
dispatch ( reblog ( { statusId : status . get ( 'id' ) , visibility : privacy } ) ) ;
2019-06-12 16:46:39 +09:00
}
2023-02-04 04:52:07 +09:00
} ;
2017-12-10 03:41:24 +09:00
2017-05-12 21:44:10 +09:00
handleReblogClick = ( status , e ) => {
2019-06-08 01:38:07 +09:00
const { settings , dispatch } = this . props ;
2024-05-20 02:07:32 +09:00
const { signedIn } = this . props . identity ;
2019-06-08 01:38:07 +09:00
2022-10-07 17:14:31 +09:00
if ( signedIn ) {
if ( settings . get ( 'confirm_boost_missing_media_description' ) && status . get ( 'media_attachments' ) . some ( item => ! item . get ( 'description' ) ) && ! status . get ( 'reblogged' ) ) {
2024-03-29 00:33:15 +09:00
dispatch ( openModal ( { modalType : 'BOOST' , modalProps : { status , onReblog : this . handleModalReblog , missingMediaDescription : true } } ) ) ;
2022-10-07 17:14:31 +09:00
} else if ( ( e && e . shiftKey ) || ! boostModal ) {
this . handleModalReblog ( status ) ;
} else {
2024-03-29 00:33:15 +09:00
dispatch ( openModal ( { modalType : 'BOOST' , modalProps : { status , onReblog : this . handleModalReblog } } ) ) ;
2022-10-07 17:14:31 +09:00
}
2017-02-17 10:33:10 +09:00
} else {
2023-05-25 22:42:37 +09:00
dispatch ( openModal ( {
modalType : 'INTERACTION' ,
modalProps : {
type : 'reblog' ,
accountId : status . getIn ( [ 'account' , 'id' ] ) ,
2023-07-27 23:11:17 +09:00
url : status . get ( 'uri' ) ,
2023-05-25 22:42:37 +09:00
} ,
2022-10-07 17:14:31 +09:00
} ) ) ;
2017-02-17 10:33:10 +09:00
}
2023-02-04 04:52:07 +09:00
} ;
2016-09-18 00:47:26 +09:00
2018-04-12 02:42:25 +09:00
handleBookmarkClick = ( status ) => {
if ( status . get ( 'bookmarked' ) ) {
this . props . dispatch ( unbookmark ( status ) ) ;
} else {
this . props . dispatch ( bookmark ( status ) ) ;
}
2023-02-04 04:52:07 +09:00
} ;
2018-04-12 02:42:25 +09:00
2018-08-31 23:41:58 +09:00
handleDeleteClick = ( status , history , withRedraft = false ) => {
2017-04-23 11:39:50 +09:00
const { dispatch , intl } = this . props ;
2017-10-30 00:10:15 +09:00
if ( ! deleteModal ) {
2018-08-31 23:41:58 +09:00
dispatch ( deleteStatus ( status . get ( 'id' ) , history , withRedraft ) ) ;
2017-05-30 00:56:13 +09:00
} else {
2023-05-25 22:42:37 +09:00
dispatch ( openModal ( {
modalType : 'CONFIRM' ,
modalProps : {
message : intl . formatMessage ( withRedraft ? messages . redraftMessage : messages . deleteMessage ) ,
confirm : intl . formatMessage ( withRedraft ? messages . redraftConfirm : messages . deleteConfirm ) ,
onConfirm : ( ) => dispatch ( deleteStatus ( status . get ( 'id' ) , history , withRedraft ) ) ,
} ,
2017-05-30 00:56:13 +09:00
} ) ) ;
}
2023-02-04 04:52:07 +09:00
} ;
2016-10-10 05:19:15 +09:00
2022-02-10 08:15:30 +09:00
handleEditClick = ( status , history ) => {
this . props . dispatch ( editStatus ( status . get ( 'id' ) , history ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2022-02-10 08:15:30 +09:00
2023-10-20 02:44:55 +09:00
handleDirectClick = ( account , history ) => {
this . props . dispatch ( directCompose ( account , history ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2018-04-11 04:38:02 +09:00
2023-10-20 02:44:55 +09:00
handleMentionClick = ( account , history ) => {
this . props . dispatch ( mentionCompose ( account , history ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2016-10-25 00:11:02 +09:00
2023-05-11 19:41:55 +09:00
handleOpenMedia = ( media , index , lang ) => {
2023-05-25 22:42:37 +09:00
this . props . dispatch ( openModal ( {
modalType : 'MEDIA' ,
modalProps : { statusId : this . props . status . get ( 'id' ) , media , index , lang } ,
} ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2016-10-25 01:07:40 +09:00
2023-05-11 19:41:55 +09:00
handleOpenVideo = ( media , lang , options ) => {
2023-05-25 22:42:37 +09:00
this . props . dispatch ( openModal ( {
modalType : 'VIDEO' ,
modalProps : { statusId : this . props . status . get ( 'id' ) , media , lang , options } ,
} ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-04-13 22:04:18 +09:00
2019-11-30 01:02:36 +09:00
handleHotkeyOpenMedia = e => {
const { status } = this . props ;
e . preventDefault ( ) ;
if ( status . get ( 'media_attachments' ) . size > 0 ) {
2020-11-27 11:24:11 +09:00
if ( status . getIn ( [ 'media_attachments' , 0 , 'type' ] ) === 'video' ) {
2020-04-25 19:16:05 +09:00
this . handleOpenVideo ( status . getIn ( [ 'media_attachments' , 0 ] ) , { startTime : 0 } ) ;
2019-11-30 01:02:36 +09:00
} else {
this . handleOpenMedia ( status . get ( 'media_attachments' ) , 0 ) ;
}
}
2023-02-04 04:52:07 +09:00
} ;
2019-11-30 01:02:36 +09:00
2017-12-27 01:13:38 +09:00
handleMuteClick = ( account ) => {
this . props . dispatch ( initMuteModal ( account ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-12-27 01:13:38 +09:00
handleConversationMuteClick = ( status ) => {
if ( status . get ( 'muted' ) ) {
this . props . dispatch ( unmuteStatus ( status . get ( 'id' ) ) ) ;
} else {
this . props . dispatch ( muteStatus ( status . get ( 'id' ) ) ) ;
}
2023-02-04 04:52:07 +09:00
} ;
2017-12-27 01:13:38 +09:00
2018-03-29 02:56:46 +09:00
handleToggleAll = ( ) => {
2022-07-25 03:01:30 +09:00
const { status , ancestorsIds , descendantsIds , settings } = this . props ;
const statusIds = [ status . get ( 'id' ) ] . concat ( ancestorsIds . toJS ( ) , descendantsIds . toJS ( ) ) ;
let { isExpanded } = this . state ;
if ( settings . getIn ( [ 'content_warnings' , 'shared_state' ] ) )
isExpanded = ! status . get ( 'hidden' ) ;
if ( ! isExpanded ) {
this . props . dispatch ( revealStatus ( statusIds ) ) ;
} else {
this . props . dispatch ( hideStatus ( statusIds ) ) ;
}
2018-03-29 02:56:46 +09:00
this . setState ( { isExpanded : ! isExpanded , threadExpanded : ! isExpanded } ) ;
2023-02-04 04:52:07 +09:00
} ;
2018-03-29 02:56:46 +09:00
2022-09-24 06:00:12 +09:00
handleTranslate = status => {
const { dispatch } = this . props ;
if ( status . get ( 'translation' ) ) {
2023-06-01 07:10:21 +09:00
dispatch ( undoStatusTranslation ( status . get ( 'id' ) , status . get ( 'poll' ) ) ) ;
2022-09-24 06:00:12 +09:00
} else {
dispatch ( translateStatus ( status . get ( 'id' ) ) ) ;
}
2023-02-04 04:52:07 +09:00
} ;
2022-09-24 06:00:12 +09:00
2019-03-27 01:34:02 +09:00
handleBlockClick = ( status ) => {
2019-09-30 04:46:05 +09:00
const { dispatch } = this . props ;
2019-03-27 01:34:02 +09:00
const account = status . get ( 'account' ) ;
2019-09-30 04:46:05 +09:00
dispatch ( initBlockModal ( account ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-12-27 01:13:38 +09:00
2017-05-12 21:44:10 +09:00
handleReport = ( status ) => {
2017-02-15 04:59:26 +09:00
this . props . dispatch ( initReport ( status . get ( 'account' ) , status ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-02-15 04:59:26 +09:00
2017-08-31 10:38:35 +09:00
handleEmbed = ( status ) => {
2023-05-25 22:42:37 +09:00
this . props . dispatch ( openModal ( {
modalType : 'EMBED' ,
2023-07-13 22:53:03 +09:00
modalProps : { id : status . get ( 'id' ) } ,
2023-05-25 22:42:37 +09:00
} ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-08-31 10:38:35 +09:00
2019-05-27 01:58:14 +09:00
handleHotkeyToggleSensitive = ( ) => {
this . handleToggleMediaVisibility ( ) ;
2023-02-04 04:52:07 +09:00
} ;
2019-05-27 01:58:14 +09:00
2017-10-06 08:07:59 +09:00
handleHotkeyMoveUp = ( ) => {
this . handleMoveUp ( this . props . status . get ( 'id' ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
handleHotkeyMoveDown = ( ) => {
this . handleMoveDown ( this . props . status . get ( 'id' ) ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
handleHotkeyReply = e => {
e . preventDefault ( ) ;
this . handleReplyClick ( this . props . status ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
handleHotkeyFavourite = ( ) => {
this . handleFavouriteClick ( this . props . status ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
handleHotkeyBoost = ( ) => {
this . handleReblogClick ( this . props . status ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
2019-04-28 02:08:38 +09:00
handleHotkeyBookmark = ( ) => {
this . handleBookmarkClick ( this . props . status ) ;
2023-02-04 04:52:07 +09:00
} ;
2019-04-28 02:08:38 +09:00
2017-10-06 08:07:59 +09:00
handleHotkeyMention = e => {
e . preventDefault ( ) ;
this . handleMentionClick ( this . props . status ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
handleHotkeyOpenProfile = ( ) => {
2023-10-20 02:44:55 +09:00
this . props . history . push ( ` /@ ${ this . props . status . getIn ( [ 'account' , 'acct' ] ) } ` ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
handleMoveUp = id => {
const { status , ancestorsIds , descendantsIds } = this . props ;
if ( id === status . get ( 'id' ) ) {
2019-04-30 03:10:44 +09:00
this . _selectChild ( ancestorsIds . size - 1 , true ) ;
2017-10-06 08:07:59 +09:00
} else {
let index = ancestorsIds . indexOf ( id ) ;
if ( index === - 1 ) {
index = descendantsIds . indexOf ( id ) ;
2019-04-30 03:10:44 +09:00
this . _selectChild ( ancestorsIds . size + index , true ) ;
2017-10-06 08:07:59 +09:00
} else {
2019-04-30 03:10:44 +09:00
this . _selectChild ( index - 1 , true ) ;
2017-10-06 08:07:59 +09:00
}
}
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
handleMoveDown = id => {
const { status , ancestorsIds , descendantsIds } = this . props ;
if ( id === status . get ( 'id' ) ) {
2019-04-30 03:10:44 +09:00
this . _selectChild ( ancestorsIds . size + 1 , false ) ;
2017-10-06 08:07:59 +09:00
} else {
let index = ancestorsIds . indexOf ( id ) ;
if ( index === - 1 ) {
index = descendantsIds . indexOf ( id ) ;
2019-04-30 03:10:44 +09:00
this . _selectChild ( ancestorsIds . size + index + 2 , false ) ;
2017-10-06 08:07:59 +09:00
} else {
2019-04-30 03:10:44 +09:00
this . _selectChild ( index + 1 , false ) ;
2017-10-06 08:07:59 +09:00
}
}
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
2019-04-30 03:10:44 +09:00
_selectChild ( index , align _top ) {
const container = this . node ;
const element = container . querySelectorAll ( '.focusable' ) [ index ] ;
2017-10-06 08:07:59 +09:00
if ( element ) {
2019-04-30 03:10:44 +09:00
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 ) ;
}
2017-10-06 08:07:59 +09:00
element . focus ( ) ;
}
}
2019-03-09 04:34:31 +09:00
handleHeaderClick = ( ) => {
this . column . scrollTop ( ) ;
2023-02-04 04:52:07 +09:00
} ;
2019-03-09 04:34:31 +09:00
2023-04-24 15:07:03 +09:00
renderChildren ( list , ancestors ) {
const { params : { statusId } } = this . props ;
return list . map ( ( id , i ) => (
2017-10-06 08:07:59 +09:00
< StatusContainer
key = { id }
id = { id }
2018-03-29 02:56:46 +09:00
expanded = { this . state . threadExpanded }
2017-10-06 08:07:59 +09:00
onMoveUp = { this . handleMoveUp }
onMoveDown = { this . handleMoveDown }
2018-07-09 03:04:53 +09:00
contextType = 'thread'
2023-08-23 22:43:41 +09:00
previousId = { i > 0 ? list . get ( i - 1 ) : undefined }
2023-04-24 15:07:03 +09:00
nextId = { list . get ( i + 1 ) || ( ancestors && statusId ) }
rootId = { statusId }
2017-10-06 08:07:59 +09:00
/ >
) ) ;
}
2017-11-28 07:05:03 +09:00
setExpansion = value => {
2018-03-28 22:40:34 +09:00
this . setState ( { isExpanded : value } ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-11-28 07:05:03 +09:00
2024-01-04 23:17:38 +09:00
setContainerRef = c => {
2017-10-06 08:07:59 +09:00
this . node = c ;
2023-02-04 04:52:07 +09:00
} ;
2017-10-06 08:07:59 +09:00
2019-03-09 04:34:31 +09:00
setColumnRef = c => {
this . column = c ;
2023-02-04 04:52:07 +09:00
} ;
2019-03-09 04:34:31 +09:00
2024-01-04 23:17:38 +09:00
setStatusRef = c => {
this . statusNode = c ;
} ;
2023-10-09 23:46:09 +09:00
_scrollStatusIntoView ( ) {
const { status , multiColumn } = this . props ;
2017-10-12 02:21:44 +09:00
2023-10-09 23:46:09 +09:00
if ( status ) {
2024-01-04 23:17:38 +09:00
requestIdleCallback ( ( ) => {
this . statusNode ? . scrollIntoView ( true ) ;
2023-07-21 20:14:30 +09:00
// In the single-column interface, `scrollIntoView` will put the post behind the header,
// so compensate for that.
if ( ! multiColumn ) {
const offset = document . querySelector ( '.column-header__wrapper' ) ? . getBoundingClientRect ( ) ? . bottom ;
if ( offset ) {
const scrollingElement = document . scrollingElement || document . body ;
scrollingElement . scrollBy ( 0 , - offset ) ;
}
}
} ) ;
2017-10-06 08:07:59 +09:00
}
2017-04-22 03:05:35 +09:00
}
2016-09-16 07:21:51 +09:00
2023-10-09 23:46:09 +09:00
componentDidUpdate ( prevProps ) {
const { status , ancestorsIds } = this . props ;
if ( status && ( ancestorsIds . size > prevProps . ancestorsIds . size || prevProps . status ? . get ( 'id' ) !== status . get ( 'id' ) ) ) {
this . _scrollStatusIntoView ( ) ;
}
}
2017-11-07 22:24:55 +09:00
componentWillUnmount ( ) {
detachFullscreenListener ( this . onFullScreenChange ) ;
}
onFullScreenChange = ( ) => {
this . setState ( { fullscreen : isFullscreen ( ) } ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-11-07 22:24:55 +09:00
2023-10-09 19:21:02 +09:00
shouldUpdateScroll = ( prevRouterProps , { location } ) => {
// Do not change scroll when opening a modal
2023-10-10 18:35:32 +09:00
if ( location . state ? . mastodonModalKey !== prevRouterProps ? . location ? . state ? . mastodonModalKey ) {
2023-10-09 19:21:02 +09:00
return false ;
}
// Scroll to focused post if it is loaded
2024-01-04 23:17:38 +09:00
if ( this . statusNode ) {
return [ 0 , this . statusNode . offsetTop ] ;
2023-10-09 19:21:02 +09:00
}
// Do not scroll otherwise, `componentDidUpdate` will take care of that
return false ;
} ;
2016-09-16 07:21:51 +09:00
render ( ) {
2016-10-25 00:11:02 +09:00
let ancestors , descendants ;
2023-01-05 21:32:29 +09:00
const { isLoading , status , settings , ancestorsIds , descendantsIds , intl , domain , multiColumn , pictureInPicture } = this . props ;
2022-07-25 03:01:30 +09:00
const { fullscreen } = this . state ;
2016-09-16 07:21:51 +09:00
2022-10-20 21:35:29 +09:00
if ( isLoading ) {
return (
< Column >
< LoadingIndicator / >
< / Column >
) ;
}
2016-09-16 07:21:51 +09:00
if ( status === null ) {
2016-10-07 23:00:11 +09:00
return (
2023-04-12 19:44:58 +09:00
< BundleColumnError multiColumn = { multiColumn } errorType = 'routing' / >
2016-10-07 23:00:11 +09:00
) ;
2016-09-16 07:21:51 +09:00
}
2022-07-25 03:01:30 +09:00
const isExpanded = settings . getIn ( [ 'content_warnings' , 'shared_state' ] ) ? ! status . get ( 'hidden' ) : this . state . isExpanded ;
2016-10-30 23:06:43 +09:00
if ( ancestorsIds && ancestorsIds . size > 0 ) {
2023-04-24 15:07:03 +09:00
ancestors = < > { this . renderChildren ( ancestorsIds , true ) } < / > ;
2016-10-25 00:11:02 +09:00
}
2016-10-30 23:06:43 +09:00
if ( descendantsIds && descendantsIds . size > 0 ) {
2023-04-12 19:44:58 +09:00
descendants = < > { this . renderChildren ( descendantsIds ) } < / > ;
2016-10-25 00:11:02 +09:00
}
2022-10-20 21:35:29 +09:00
const isLocal = status . getIn ( [ 'account' , 'acct' ] , '' ) . indexOf ( '@' ) === - 1 ;
const isIndexable = ! status . getIn ( [ 'account' , 'noindex' ] ) ;
2017-10-06 08:07:59 +09:00
const handlers = {
moveUp : this . handleHotkeyMoveUp ,
moveDown : this . handleHotkeyMoveDown ,
reply : this . handleHotkeyReply ,
favourite : this . handleHotkeyFavourite ,
boost : this . handleHotkeyBoost ,
2019-04-28 02:08:38 +09:00
bookmark : this . handleHotkeyBookmark ,
2017-10-06 08:07:59 +09:00
mention : this . handleHotkeyMention ,
openProfile : this . handleHotkeyOpenProfile ,
2023-12-10 04:29:23 +09:00
toggleHidden : this . handleToggleHidden ,
2019-05-27 01:58:14 +09:00
toggleSensitive : this . handleHotkeyToggleSensitive ,
2019-11-30 01:02:36 +09:00
openMedia : this . handleHotkeyOpenMedia ,
2017-10-06 08:07:59 +09:00
} ;
2016-09-16 07:21:51 +09:00
return (
2019-08-02 02:17:17 +09:00
< Column bindToDocument = { ! multiColumn } ref = { this . setColumnRef } label = { intl . formatMessage ( messages . detailedStatus ) } >
2018-03-29 02:56:46 +09:00
< ColumnHeader
2019-02-27 21:47:27 +09:00
icon = 'comment'
2023-10-25 02:45:08 +09:00
iconComponent = { ChatIcon }
2019-02-27 21:47:27 +09:00
title = { intl . formatMessage ( messages . tootHeading ) }
2019-03-09 04:34:31 +09:00
onClick = { this . handleHeaderClick }
2018-03-29 02:56:46 +09:00
showBackButton
2019-08-01 19:26:58 +09:00
multiColumn = { multiColumn }
2018-03-29 02:56:46 +09:00
extraButton = { (
2023-10-25 02:45:08 +09:00
< button type = 'button' className = 'column-header__button' title = { intl . formatMessage ( ! isExpanded ? messages . revealAll : messages . hideAll ) } aria - label = { intl . formatMessage ( ! isExpanded ? messages . revealAll : messages . hideAll ) } onClick = { this . handleToggleAll } > < Icon id = { ! isExpanded ? 'eye-slash' : 'eye' } icon = { isExpanded ? VisibilityOffIcon : VisibilityIcon } / > < / button >
2018-03-29 02:56:46 +09:00
) }
/ >
2016-09-18 20:03:37 +09:00
2023-10-09 19:21:02 +09:00
< ScrollContainer scrollKey = 'thread' shouldUpdateScroll = { this . shouldUpdateScroll } >
2024-01-04 23:17:38 +09:00
< div className = { classNames ( 'scrollable' , { fullscreen } ) } ref = { this . setContainerRef } >
2016-10-25 00:11:02 +09:00
{ ancestors }
2016-09-18 20:03:37 +09:00
2017-10-06 08:07:59 +09:00
< HotKeys handlers = { handlers } >
2024-01-04 23:17:38 +09:00
< div className = { classNames ( 'focusable' , 'detailed-status__wrapper' , ` detailed-status__wrapper- ${ status . get ( 'visibility' ) } ` ) } tabIndex = { 0 } aria - label = { textForScreenReader ( intl , status , false , isExpanded ) } ref = { this . setStatusRef } >
2017-10-06 08:07:59 +09:00
< DetailedStatus
2020-01-07 02:22:17 +09:00
key = { ` details- ${ status . get ( 'id' ) } ` }
2017-10-06 08:07:59 +09:00
status = { status }
2017-10-12 02:43:10 +09:00
settings = { settings }
2017-10-06 08:07:59 +09:00
onOpenVideo = { this . handleOpenVideo }
onOpenMedia = { this . handleOpenMedia }
2017-11-28 07:05:03 +09:00
expanded = { isExpanded }
2022-07-25 03:01:30 +09:00
onToggleHidden = { this . handleToggleHidden }
2022-09-24 06:00:12 +09:00
onTranslate = { this . handleTranslate }
2019-01-20 19:47:17 +09:00
domain = { domain }
2019-05-27 01:58:14 +09:00
showMedia = { this . state . showMedia }
onToggleMediaVisibility = { this . handleToggleMediaVisibility }
2023-01-05 21:32:29 +09:00
pictureInPicture = { pictureInPicture }
2017-10-06 08:07:59 +09:00
/ >
< ActionBar
2020-01-07 02:22:17 +09:00
key = { ` action-bar- ${ status . get ( 'id' ) } ` }
2017-10-06 08:07:59 +09:00
status = { status }
onReply = { this . handleReplyClick }
onFavourite = { this . handleFavouriteClick }
onReblog = { this . handleReblogClick }
2018-04-12 02:42:25 +09:00
onBookmark = { this . handleBookmarkClick }
2017-10-06 08:07:59 +09:00
onDelete = { this . handleDeleteClick }
2022-02-10 08:15:30 +09:00
onEdit = { this . handleEditClick }
2018-04-11 04:38:02 +09:00
onDirect = { this . handleDirectClick }
2017-10-06 08:07:59 +09:00
onMention = { this . handleMentionClick }
2017-12-27 01:13:38 +09:00
onMute = { this . handleMuteClick }
onMuteConversation = { this . handleConversationMuteClick }
onBlock = { this . handleBlockClick }
2017-10-06 08:07:59 +09:00
onReport = { this . handleReport }
onPin = { this . handlePin }
onEmbed = { this . handleEmbed }
/ >
< / div >
< / HotKeys >
2016-10-20 01:20:19 +09:00
2016-10-25 00:11:02 +09:00
{ descendants }
2016-10-20 01:20:19 +09:00
< / div >
< / ScrollContainer >
2022-09-29 11:39:33 +09:00
< Helmet >
2023-04-15 00:29:09 +09:00
< title > { titleFromStatus ( intl , status ) } < / title >
2022-10-20 21:35:29 +09:00
< meta name = 'robots' content = { ( isLocal && isIndexable ) ? 'all' : 'noindex' } / >
2023-07-05 18:25:27 +09:00
< link rel = 'canonical' href = { status . get ( 'url' ) } / >
2022-09-29 11:39:33 +09:00
< / Helmet >
2016-10-07 23:00:11 +09:00
< / Column >
2016-09-16 07:21:51 +09:00
) ;
}
2017-04-22 03:05:35 +09:00
}
2023-03-25 07:15:25 +09:00
2024-05-20 02:07:32 +09:00
export default withRouter ( injectIntl ( connect ( makeMapStateToProps ) ( withIdentity ( Status ) ) ) ) ;