2017-04-22 03:05:35 +09:00
import PropTypes from 'prop-types' ;
2023-05-28 23:38:10 +09:00
2024-03-14 18:18:24 +09:00
import { FormattedDate , FormattedMessage } from 'react-intl' ;
2023-05-28 23:38:10 +09:00
import classNames from 'classnames' ;
2023-10-20 02:44:55 +09:00
import { Link , withRouter } from 'react-router-dom' ;
2023-05-28 23:38:10 +09:00
2016-09-25 21:20:29 +09:00
import ImmutablePropTypes from 'react-immutable-proptypes' ;
2023-05-28 23:38:10 +09:00
import ImmutablePureComponent from 'react-immutable-pure-component' ;
import { AnimatedNumber } from 'flavours/glitch/components/animated_number' ;
import AttachmentList from 'flavours/glitch/components/attachment_list' ;
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp' ;
2023-08-22 02:39:01 +09:00
import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar' ;
2023-05-28 23:38:10 +09:00
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder' ;
2023-10-25 02:45:08 +09:00
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon' ;
2019-03-04 06:18:23 +09:00
import PollContainer from 'flavours/glitch/containers/poll_container' ;
2023-10-20 02:44:55 +09:00
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router' ;
2023-05-28 23:38:10 +09:00
2023-11-15 20:01:51 +09:00
import { Avatar } from '../../../components/avatar' ;
import { DisplayName } from '../../../components/display_name' ;
import MediaGallery from '../../../components/media_gallery' ;
import StatusContent from '../../../components/status_content' ;
import Audio from '../../audio' ;
2023-05-28 23:38:10 +09:00
import scheduleIdleTask from '../../ui/util/schedule_idle_task' ;
2023-11-15 20:01:51 +09:00
import Video from '../../video' ;
2023-05-28 23:38:10 +09:00
import Card from './card' ;
2022-01-20 06:37:27 +09:00
class DetailedStatus extends ImmutablePureComponent {
2016-09-25 21:20:29 +09:00
2017-05-12 21:44:10 +09:00
static propTypes = {
2019-03-13 01:34:00 +09:00
status : ImmutablePropTypes . map ,
2017-06-30 18:15:18 +09:00
settings : ImmutablePropTypes . map . isRequired ,
2017-05-12 21:44:10 +09:00
onOpenMedia : PropTypes . func . isRequired ,
onOpenVideo : PropTypes . func . isRequired ,
2019-02-06 22:13:15 +09:00
onToggleHidden : PropTypes . func ,
2022-09-24 06:00:12 +09:00
onTranslate : PropTypes . func . isRequired ,
2018-03-29 02:56:46 +09:00
expanded : PropTypes . bool ,
2019-01-20 02:55:27 +09:00
measureHeight : PropTypes . bool ,
onHeightChange : PropTypes . func ,
domain : PropTypes . string . isRequired ,
2019-01-20 19:47:17 +09:00
compact : PropTypes . bool ,
2019-05-27 01:58:14 +09:00
showMedia : PropTypes . bool ,
2023-01-05 21:32:29 +09:00
pictureInPicture : ImmutablePropTypes . contains ( {
inUse : PropTypes . bool ,
available : PropTypes . bool ,
} ) ,
2019-05-27 01:58:14 +09:00
onToggleMediaVisibility : PropTypes . func ,
2023-10-20 02:44:55 +09:00
... WithRouterPropTypes ,
2019-01-20 02:55:27 +09:00
} ;
state = {
height : null ,
2017-05-12 21:44:10 +09:00
} ;
handleAccountClick = ( e ) => {
2023-10-20 02:44:55 +09:00
if ( e . button === 0 && ! ( e . ctrlKey || e . altKey || e . metaKey ) && this . props . history ) {
2016-09-25 21:20:29 +09:00
e . preventDefault ( ) ;
2023-10-20 02:44:55 +09:00
this . props . history . push ( ` /@ ${ this . props . status . getIn ( [ 'account' , 'acct' ] ) } ` ) ;
2016-09-25 21:20:29 +09:00
}
e . stopPropagation ( ) ;
2023-02-04 04:52:07 +09:00
} ;
2016-09-25 21:20:29 +09:00
2018-12-04 00:16:05 +09:00
parseClick = ( e , destination ) => {
2023-10-20 02:44:55 +09:00
if ( e . button === 0 && ! ( e . ctrlKey || e . altKey || e . metaKey ) && this . props . history ) {
2018-12-04 00:16:05 +09:00
e . preventDefault ( ) ;
2023-10-20 02:44:55 +09:00
this . props . history . push ( destination ) ;
2018-12-04 00:16:05 +09:00
}
e . stopPropagation ( ) ;
2023-02-04 04:52:07 +09:00
} ;
2018-12-04 00:16:05 +09:00
2020-12-07 12:29:37 +09:00
handleOpenVideo = ( options ) => {
this . props . onOpenVideo ( this . props . status . getIn ( [ 'media_attachments' , 0 ] ) , options ) ;
2023-02-04 04:52:07 +09:00
} ;
2017-09-14 10:39:10 +09:00
2019-01-20 02:55:27 +09:00
_measureHeight ( heightJustChanged ) {
if ( this . props . measureHeight && this . node ) {
2019-01-20 19:52:06 +09:00
scheduleIdleTask ( ( ) => this . node && this . setState ( { height : Math . ceil ( this . node . scrollHeight ) + 1 } ) ) ;
2019-01-20 02:55:27 +09:00
if ( this . props . onHeightChange && heightJustChanged ) {
this . props . onHeightChange ( ) ;
}
}
}
setRef = c => {
this . node = c ;
this . _measureHeight ( ) ;
2023-02-04 04:52:07 +09:00
} ;
2019-01-20 02:55:27 +09:00
componentDidUpdate ( prevProps , prevState ) {
this . _measureHeight ( prevState . height !== this . state . height ) ;
}
2019-02-06 23:11:46 +09:00
handleChildUpdate = ( ) => {
this . _measureHeight ( ) ;
2023-02-04 04:52:07 +09:00
} ;
2019-02-06 23:11:46 +09:00
2019-01-20 02:55:27 +09:00
handleModalLink = e => {
e . preventDefault ( ) ;
let href ;
if ( e . target . nodeName !== 'A' ) {
href = e . target . parentNode . href ;
} else {
href = e . target . href ;
}
window . open ( href , 'mastodon-intent' , 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes' ) ;
2023-02-04 04:52:07 +09:00
} ;
2019-01-20 02:55:27 +09:00
2022-09-24 06:00:12 +09:00
handleTranslate = ( ) => {
const { onTranslate , status } = this . props ;
onTranslate ( status ) ;
2023-02-04 04:52:07 +09:00
} ;
2022-09-24 06:00:12 +09:00
2016-09-25 21:20:29 +09:00
render ( ) {
2019-02-17 22:28:25 +09:00
const status = ( this . props . status && this . props . status . get ( 'reblog' ) ) ? this . props . status . get ( 'reblog' ) : this . props . status ;
2019-01-20 02:55:27 +09:00
const outerStyle = { boxSizing : 'border-box' } ;
2023-05-08 01:22:25 +09:00
const { compact , pictureInPicture , expanded , onToggleHidden , settings } = this . props ;
2019-01-20 02:55:27 +09:00
if ( ! status ) {
return null ;
}
2017-01-15 22:01:33 +09:00
let applicationLink = '' ;
2017-10-16 21:10:12 +09:00
let reblogLink = '' ;
2019-01-20 02:55:27 +09:00
let favouriteLink = '' ;
2022-07-24 18:10:49 +09:00
// Depending on user settings, some media are considered as parts of the
// contents (affected by CW) while other will be displayed outside of the
// CW.
let contentMedia = [ ] ;
let contentMediaIcons = [ ] ;
let extraMedia = [ ] ;
let extraMediaIcons = [ ] ;
let media = contentMedia ;
let mediaIcons = contentMediaIcons ;
if ( settings . getIn ( [ 'content_warnings' , 'media_outside' ] ) ) {
media = extraMedia ;
mediaIcons = extraMediaIcons ;
}
2019-01-20 02:55:27 +09:00
if ( this . props . measureHeight ) {
outerStyle . height = ` ${ this . state . height } px ` ;
}
2016-09-25 21:58:07 +09:00
2023-06-01 07:10:21 +09:00
const language = status . getIn ( [ 'translation' , 'language' ] ) || status . get ( 'language' ) ;
2023-01-05 21:32:29 +09:00
if ( pictureInPicture . get ( 'inUse' ) ) {
2021-09-08 21:39:14 +09:00
media . push ( < PictureInPicturePlaceholder / > ) ;
mediaIcons . push ( 'video-camera' ) ;
2019-03-04 06:18:23 +09:00
} else if ( status . get ( 'media_attachments' ) . size > 0 ) {
2017-04-19 22:37:18 +09:00
if ( status . get ( 'media_attachments' ) . some ( item => item . get ( 'type' ) === 'unknown' ) ) {
2021-09-08 21:39:14 +09:00
media . push ( < AttachmentList media = { status . get ( 'media_attachments' ) } / > ) ;
2019-08-24 05:38:02 +09:00
} else if ( status . getIn ( [ 'media_attachments' , 0 , 'type' ] ) === 'audio' ) {
const attachment = status . getIn ( [ 'media_attachments' , 0 ] ) ;
2023-06-01 07:10:21 +09:00
const description = attachment . getIn ( [ 'translation' , 'description' ] ) || attachment . get ( 'description' ) ;
2019-08-24 05:38:02 +09:00
2021-09-08 21:39:14 +09:00
media . push (
2019-08-24 05:38:02 +09:00
< Audio
src = { attachment . get ( 'url' ) }
2023-06-01 07:10:21 +09:00
alt = { description }
lang = { language }
2019-08-24 05:38:02 +09:00
duration = { attachment . getIn ( [ 'meta' , 'original' , 'duration' ] , 0 ) }
2020-06-29 20:56:55 +09:00
poster = { attachment . get ( 'preview_url' ) || status . getIn ( [ 'account' , 'avatar_static' ] ) }
2020-07-06 01:28:25 +09:00
backgroundColor = { attachment . getIn ( [ 'meta' , 'colors' , 'background' ] ) }
foregroundColor = { attachment . getIn ( [ 'meta' , 'colors' , 'foreground' ] ) }
accentColor = { attachment . getIn ( [ 'meta' , 'colors' , 'accent' ] ) }
2022-08-13 22:39:05 +09:00
sensitive = { status . get ( 'sensitive' ) }
visible = { this . props . showMedia }
blurhash = { attachment . get ( 'blurhash' ) }
2020-06-23 19:20:14 +09:00
height = { 150 }
2022-08-13 22:39:05 +09:00
onToggleVisibility = { this . props . onToggleMediaVisibility }
2021-09-08 21:39:14 +09:00
/ > ,
2019-08-24 05:38:02 +09:00
) ;
2021-09-08 21:39:14 +09:00
mediaIcons . push ( 'music' ) ;
2019-08-24 05:38:02 +09:00
} else if ( status . getIn ( [ 'media_attachments' , 0 , 'type' ] ) === 'video' ) {
2019-06-20 06:42:38 +09:00
const attachment = status . getIn ( [ 'media_attachments' , 0 ] ) ;
2023-06-01 07:10:21 +09:00
const description = attachment . getIn ( [ 'translation' , 'description' ] ) || attachment . get ( 'description' ) ;
2021-09-08 21:39:14 +09:00
media . push (
2017-11-18 12:11:18 +09:00
< Video
2019-06-20 06:42:38 +09:00
preview = { attachment . get ( 'preview_url' ) }
2020-11-22 07:19:04 +09:00
frameRate = { attachment . getIn ( [ 'meta' , 'original' , 'frame_rate' ] ) }
2019-06-20 06:42:38 +09:00
blurhash = { attachment . get ( 'blurhash' ) }
src = { attachment . get ( 'url' ) }
2023-06-01 07:10:21 +09:00
alt = { description }
lang = { language }
2018-04-15 00:14:04 +09:00
inline
2017-09-14 10:39:10 +09:00
sensitive = { status . get ( 'sensitive' ) }
2017-06-30 18:15:18 +09:00
letterbox = { settings . getIn ( [ 'media' , 'letterbox' ] ) }
2017-09-23 04:47:14 +09:00
fullwidth = { settings . getIn ( [ 'media' , 'fullwidth' ] ) }
2018-11-23 22:12:15 +09:00
preventPlayback = { ! expanded }
2018-04-09 07:11:10 +09:00
onOpenVideo = { this . handleOpenVideo }
2017-06-30 18:15:18 +09:00
autoplay
2019-05-27 01:58:14 +09:00
visible = { this . props . showMedia }
onToggleVisibility = { this . props . onToggleMediaVisibility }
2021-09-08 21:39:14 +09:00
/ > ,
2017-09-14 10:39:10 +09:00
) ;
2021-09-08 21:39:14 +09:00
mediaIcons . push ( 'video-camera' ) ;
2016-09-25 21:58:07 +09:00
} else {
2021-09-08 21:39:14 +09:00
media . push (
2018-04-15 04:22:13 +09:00
< MediaGallery
standalone
2017-09-24 12:58:30 +09:00
sensitive = { status . get ( 'sensitive' ) }
media = { status . get ( 'media_attachments' ) }
2023-06-01 07:10:21 +09:00
lang = { language }
2017-06-30 18:15:18 +09:00
letterbox = { settings . getIn ( [ 'media' , 'letterbox' ] ) }
2018-09-04 03:46:00 +09:00
fullwidth = { settings . getIn ( [ 'media' , 'fullwidth' ] ) }
2018-11-23 22:12:15 +09:00
hidden = { ! expanded }
2017-09-24 12:58:30 +09:00
onOpenMedia = { this . props . onOpenMedia }
2019-05-27 01:58:14 +09:00
visible = { this . props . showMedia }
onToggleVisibility = { this . props . onToggleMediaVisibility }
2021-09-08 21:39:14 +09:00
/ > ,
2017-09-24 12:58:30 +09:00
) ;
2021-09-08 21:39:14 +09:00
mediaIcons . push ( 'picture-o' ) ;
2016-09-25 21:58:07 +09:00
}
2019-05-06 04:09:45 +09:00
} else if ( status . get ( 'card' ) ) {
2021-09-08 21:39:14 +09:00
media . push ( < Card sensitive = { status . get ( 'sensitive' ) } onOpenMedia = { this . props . onOpenMedia } card = { status . get ( 'card' ) } / > ) ;
mediaIcons . push ( 'link' ) ;
2019-05-04 03:25:57 +09:00
}
2016-09-25 21:20:29 +09:00
2022-04-21 04:29:31 +09:00
if ( status . get ( 'poll' ) ) {
2023-02-27 04:13:27 +09:00
contentMedia . push ( < PollContainer pollId = { status . get ( 'poll' ) } lang = { status . get ( 'language' ) } / > ) ;
2022-07-24 18:10:49 +09:00
contentMediaIcons . push ( 'tasks' ) ;
2022-04-21 04:29:31 +09:00
}
2017-01-15 22:01:33 +09:00
if ( status . get ( 'application' ) ) {
2024-03-14 18:18:24 +09:00
applicationLink = < > · < a className = 'detailed-status__application' href = { status . getIn ( [ 'application' , 'website' ] ) } target = '_blank' rel = 'noopener noreferrer' > { status . getIn ( [ 'application' , 'name' ] ) } < / a > < / > ;
2017-01-15 22:01:33 +09:00
}
2024-03-14 18:18:24 +09:00
const visibilityLink = < > · < VisibilityIcon visibility = { status . get ( 'visibility' ) } / > < / > ;
2020-08-25 01:32:03 +09:00
2020-03-11 01:46:47 +09:00
if ( ! [ 'unlisted' , 'public' ] . includes ( status . get ( 'visibility' ) ) ) {
reblogLink = null ;
2023-10-20 02:44:55 +09:00
} else if ( this . props . history ) {
2019-01-20 02:55:27 +09:00
reblogLink = (
2024-03-14 18:18:24 +09:00
< Link to = { ` /@ ${ status . getIn ( [ 'account' , 'acct' ] ) } / ${ status . get ( 'id' ) } /reblogs ` } className = 'detailed-status__link' >
< span className = 'detailed-status__reblogs' >
< AnimatedNumber value = { status . get ( 'reblogs_count' ) } / >
< / span >
< FormattedMessage id = 'status.reblogs' defaultMessage = '{count, plural, one {boost} other {boosts}}' values = { { count : status . get ( 'reblogs_count' ) } } / >
< / Link >
2019-01-20 02:55:27 +09:00
) ;
} else {
reblogLink = (
2024-03-14 18:18:24 +09:00
< a href = { ` /interact/ ${ status . get ( 'id' ) } ?type=reblog ` } className = 'detailed-status__link' onClick = { this . handleModalLink } >
< span className = 'detailed-status__reblogs' >
< AnimatedNumber value = { status . get ( 'reblogs_count' ) } / >
< / span >
< FormattedMessage id = 'status.reblogs' defaultMessage = '{count, plural, one {boost} other {boosts}}' values = { { count : status . get ( 'reblogs_count' ) } } / >
< / a >
2019-01-20 02:55:27 +09:00
) ;
}
2023-10-20 02:44:55 +09:00
if ( this . props . history ) {
2019-01-20 02:55:27 +09:00
favouriteLink = (
2021-09-26 12:46:13 +09:00
< Link to = { ` /@ ${ status . getIn ( [ 'account' , 'acct' ] ) } / ${ status . get ( 'id' ) } /favourites ` } className = 'detailed-status__link' >
2019-01-20 02:55:27 +09:00
< span className = 'detailed-status__favorites' >
2020-01-25 13:23:05 +09:00
< AnimatedNumber value = { status . get ( 'favourites_count' ) } / >
2019-01-20 02:55:27 +09:00
< / span >
2024-03-14 18:18:24 +09:00
< FormattedMessage id = 'status.favourites' defaultMessage = '{count, plural, one {favorite} other {favorites}}' values = { { count : status . get ( 'favourites_count' ) } } / >
2019-01-20 02:55:27 +09:00
< / Link >
) ;
2017-10-16 21:10:12 +09:00
} else {
2019-01-20 02:55:27 +09:00
favouriteLink = (
< a href = { ` /interact/ ${ status . get ( 'id' ) } ?type=favourite ` } className = 'detailed-status__link' onClick = { this . handleModalLink } >
< span className = 'detailed-status__favorites' >
2020-01-25 13:23:05 +09:00
< AnimatedNumber value = { status . get ( 'favourites_count' ) } / >
2019-01-20 02:55:27 +09:00
< / span >
2024-03-14 18:18:24 +09:00
< FormattedMessage id = 'status.favourites' defaultMessage = '{count, plural, one {favorite} other {favorites}}' values = { { count : status . get ( 'favourites_count' ) } } / >
2019-01-20 02:55:27 +09:00
< / a >
) ;
2017-10-16 21:10:12 +09:00
}
2023-08-22 02:39:01 +09:00
const { statusContentProps , hashtagBar } = getHashtagBarForStatus ( status ) ;
2023-08-26 04:54:15 +09:00
contentMedia . push ( hashtagBar ) ;
2023-08-22 02:39:01 +09:00
2016-09-25 21:20:29 +09:00
return (
2019-01-20 19:47:17 +09:00
< div style = { outerStyle } >
2020-08-25 01:32:03 +09:00
< div ref = { this . setRef } className = { classNames ( 'detailed-status' , ` detailed-status- ${ status . get ( 'visibility' ) } ` , { compact } ) } data - status - by = { status . getIn ( [ 'account' , 'acct' ] ) } >
2019-01-20 19:47:17 +09:00
< a href = { status . getIn ( [ 'account' , 'url' ] ) } onClick = { this . handleAccountClick } className = 'detailed-status__display-name' >
< div className = 'detailed-status__display-avatar' > < Avatar account = { status . get ( 'account' ) } size = { 48 } / > < / div >
< DisplayName account = { status . get ( 'account' ) } localDomain = { this . props . domain } / >
< / a >
< StatusContent
status = { status }
2022-07-24 18:10:49 +09:00
media = { contentMedia }
2022-07-24 20:16:03 +09:00
extraMedia = { extraMedia }
2022-07-24 18:10:49 +09:00
mediaIcons = { contentMediaIcons }
2019-01-20 19:47:17 +09:00
expanded = { expanded }
collapsed = { false }
onExpandedToggle = { onToggleHidden }
2022-09-24 06:00:12 +09:00
onTranslate = { this . handleTranslate }
2019-01-20 19:47:17 +09:00
parseClick = { this . parseClick }
2019-02-06 23:11:46 +09:00
onUpdate = { this . handleChildUpdate }
2019-08-02 01:48:16 +09:00
tagLinks = { settings . get ( 'tag_misleading_links' ) }
2019-08-29 05:13:41 +09:00
rewriteMentions = { settings . get ( 'rewrite_mentions' ) }
2019-04-04 02:29:51 +09:00
disabled
2023-08-22 02:39:01 +09:00
{ ... statusContentProps }
2019-01-20 19:47:17 +09:00
/ >
2016-09-25 21:20:29 +09:00
2019-01-20 19:47:17 +09:00
< div className = 'detailed-status__meta' >
2024-03-14 18:18:24 +09:00
< div className = 'detailed-status__meta__line' >
< a className = 'detailed-status__datetime' href = { status . get ( 'url' ) } target = '_blank' rel = 'noopener noreferrer' >
< FormattedDate value = { new Date ( status . get ( 'created_at' ) ) } year = 'numeric' month = 'short' day = '2-digit' hour = '2-digit' minute = '2-digit' / >
< / a >
{ visibilityLink }
{ applicationLink }
< / div >
{ status . get ( 'edited_at' ) && < div className = 'detailed-status__meta__line' > < EditedTimestamp statusId = { status . get ( 'id' ) } timestamp = { status . get ( 'edited_at' ) } / > < / div > }
< div className = 'detailed-status__meta__line' >
{ reblogLink }
2024-03-16 10:11:59 +09:00
{ reblogLink && < > · < / > }
2024-03-14 18:18:24 +09:00
{ favouriteLink }
< / div >
2019-01-20 19:47:17 +09:00
< / div >
2016-09-25 21:20:29 +09:00
< / div >
< / div >
) ;
}
2017-04-22 03:05:35 +09:00
}
2023-03-25 07:15:25 +09:00
2023-10-25 02:45:08 +09:00
export default withRouter ( DetailedStatus ) ;