2020-08-17 21:07:16 +01:00
import React from 'react'
2020-08-17 21:59:29 +01:00
import PropTypes from 'prop-types'
2020-08-17 21:39:25 +01:00
import { connect } from 'react-redux'
2020-03-04 03:45:16 +00:00
import { NavLink } from 'react-router-dom'
2020-05-02 07:25:55 +01:00
import { defineMessages , injectIntl , FormattedMessage } from 'react-intl'
2020-04-24 04:17:27 +01:00
import ImmutablePropTypes from 'react-immutable-proptypes'
2020-03-04 03:45:16 +00:00
import ImmutablePureComponent from 'react-immutable-pure-component'
2020-05-02 07:25:55 +01:00
import {
favorite ,
unfavorite ,
2020-12-09 20:02:43 +00:00
repost ,
unrepost ,
2020-05-02 07:25:55 +01:00
} from '../actions/interactions'
import { replyCompose } from '../actions/compose'
import { openModal } from '../actions/modal'
import { openPopover } from '../actions/popover'
import { makeGetStatus } from '../selectors'
2020-05-05 06:16:01 +01:00
import {
CX ,
2020-12-09 20:02:43 +00:00
MODAL _BOOST ,
2020-05-05 06:16:01 +01:00
} from '../constants'
2020-12-09 20:02:43 +00:00
import { me , boostModal } from '../initial_state'
2020-03-04 03:45:16 +00:00
import Avatar from './avatar'
import Button from './button'
2020-04-24 04:17:27 +01:00
import CommentHeader from './comment_header'
2020-03-08 23:02:28 +00:00
import StatusContent from './status_content'
2020-05-02 07:25:55 +01:00
import StatusMedia from './status_media'
import { defaultMediaVisibility } from './status'
2020-04-24 04:17:27 +01:00
import Text from './text'
2020-03-04 03:45:16 +00:00
class Comment extends ImmutablePureComponent {
2020-05-15 04:17:31 +01:00
static contextTypes = {
router : PropTypes . object ,
}
2020-05-02 07:25:55 +01:00
state = {
showMedia : defaultMediaVisibility ( this . props . status ) ,
2020-05-04 19:44:37 +01:00
statusId : undefined ,
height : undefined ,
2020-05-02 07:25:55 +01:00
}
2020-05-07 00:40:54 +01:00
handleClick = ( ) => {
//
}
2020-05-02 07:25:55 +01:00
handleOnReply = ( ) => {
2020-05-15 04:17:31 +01:00
this . props . onReply ( this . props . status , this . context . router )
2020-05-02 07:25:55 +01:00
}
handleOnFavorite = ( ) => {
this . props . onFavorite ( this . props . status )
}
2020-12-09 20:02:43 +00:00
handleOnRepost = ( ) => {
this . props . onRepost ( this . props . status )
}
2020-05-02 07:25:55 +01:00
handleOnOpenStatusOptions = ( ) => {
2020-05-07 00:40:54 +01:00
this . props . onOpenStatusOptions ( this . moreNode , this . props . status )
}
2020-06-05 14:14:09 +01:00
handleToggleMediaVisibility = ( ) => {
this . setState ( { showMedia : ! this . state . showMedia } )
}
2020-12-09 20:02:43 +00:00
handleOnThreadMouseEnter = ( event ) => {
if ( event . target ) {
const threadKey = event . target . getAttribute ( 'data-threader-indent' )
const elems = document . querySelectorAll ( ` [data-threader-indent=" ${ threadKey } "] ` )
elems . forEach ( ( elem ) => elem . classList . add ( 'thread-hovering' ) )
}
}
handleOnThreadMouseLeave = ( event ) => {
if ( event . target ) {
const threadKey = event . target . getAttribute ( 'data-threader-indent' )
const elems = document . querySelectorAll ( ` [data-threader-indent=" ${ threadKey } "] ` )
elems . forEach ( ( elem ) => elem . classList . remove ( 'thread-hovering' ) )
}
}
handleOnThreadClick = ( event ) => {
// : todo :
}
2020-05-07 00:40:54 +01:00
setMoreNode = ( c ) => {
this . moreNode = c
2020-05-02 07:25:55 +01:00
}
2020-12-21 23:30:46 +00:00
setContainerNode = ( c ) => {
this . containerNode = c
if ( this . props . isHighlighted && this . containerNode ) {
this . containerNode . scrollIntoView ( { behavior : 'smooth' } ) ;
}
}
2020-03-04 03:45:16 +00:00
render ( ) {
2020-04-24 04:17:27 +01:00
const {
indent ,
intl ,
status ,
2020-05-02 07:25:55 +01:00
isHidden ,
2020-05-05 06:16:01 +01:00
isHighlighted ,
2020-12-10 06:18:19 +00:00
isDetached ,
2020-05-05 06:16:01 +01:00
ancestorAccountId ,
2020-04-24 04:17:27 +01:00
} = this . props
2020-03-04 03:45:16 +00:00
2021-02-18 21:24:06 +00:00
if ( ! status ) return null
2021-02-18 21:25:07 +00:00
//If account is spam and not mine, hide
if ( status . getIn ( [ 'account' , 'is_flagged_as_spam' ] ) && status . getIn ( [ 'account' , 'id' ] ) !== me ) {
return null
}
2020-05-02 07:25:55 +01:00
if ( isHidden ) {
return (
2020-12-21 23:30:46 +00:00
< div tabIndex = '0' ref = { this . setContainerNode } >
2020-05-02 07:25:55 +01:00
{ status . getIn ( [ 'account' , 'display_name' ] ) || status . getIn ( [ 'account' , 'username' ] ) }
{ status . get ( 'content' ) }
< / d i v >
)
}
2020-04-08 02:06:59 +01:00
const style = {
2020-12-09 20:02:43 +00:00
paddingLeft : ` ${ indent * 38 } px ` ,
2020-04-08 02:06:59 +01:00
}
2020-12-10 06:18:19 +00:00
const containerClasses = CX ( {
d : 1 ,
px15 : 1 ,
2021-01-05 16:34:51 +00:00
pt5 : ! isDetached ,
2020-12-10 06:18:19 +00:00
pt10 : isDetached ,
pb5 : isDetached ,
borderBottom1PX : isDetached ,
borderColorSecondary : isDetached ,
} )
2020-05-05 06:16:01 +01:00
const contentClasses = CX ( {
2020-08-18 21:49:11 +01:00
d : 1 ,
2020-05-05 06:16:01 +01:00
px10 : 1 ,
pt5 : 1 ,
pb10 : 1 ,
radiusSmall : 1 ,
bgSubtle : ! isHighlighted ,
highlightedComment : isHighlighted ,
} )
2020-03-04 03:45:16 +00:00
return (
2020-12-21 23:30:46 +00:00
< div
className = { containerClasses }
data - comment = { status . get ( 'id' ) }
ref = { this . setContainerNode }
>
2020-12-09 20:02:43 +00:00
{
indent > 0 &&
< div className = { [ _s . d , _s . z3 , _s . flexRow , _s . posAbs , _s . topNeg20PX , _s . left0 , _s . bottom20PX , _s . aiCenter , _s . jcCenter ] . join ( ' ' ) } >
{ Array . apply ( null , {
length : indent
} ) . map ( ( _ , i ) => (
< button
key = { ` thread- ${ status . get ( 'id' ) } - ${ i } ` }
data - threader
data - threader - indent = { i }
onMouseEnter = { this . handleOnThreadMouseEnter }
onMouseLeave = { this . handleOnThreadMouseLeave }
onClick = { this . handleOnThreadClick }
className = { [ _s . d , _s . w14PX , _s . h100PC , _s . outlineNone , _s . bgTransparent , _s . ml20 , _s . cursorPointer ] . join ( ' ' ) }
>
< span className = { [ _s . d , _s . w2PX , _s . h100PC , _s . mlAuto , _s . mr2 , _s . bgSubtle ] . join ( ' ' ) } / >
< / b u t t o n >
) ) }
< / d i v >
}
2021-01-05 16:34:51 +00:00
< div className = { [ _s . d , _s . mb5 ] . join ( ' ' ) } style = { style } >
2020-03-08 23:02:28 +00:00
2020-08-18 21:49:11 +01:00
< div className = { [ _s . d , _s . flexRow ] . join ( ' ' ) } >
2020-03-08 23:02:28 +00:00
< NavLink
to = { ` / ${ status . getIn ( [ 'account' , 'acct' ] ) } ` }
title = { status . getIn ( [ 'account' , 'acct' ] ) }
2020-08-18 21:49:11 +01:00
className = { [ _s . d , _s . mr10 , _s . pt5 ] . join ( ' ' ) }
2020-03-08 23:02:28 +00:00
>
2020-12-09 20:02:43 +00:00
< Avatar account = { status . get ( 'account' ) } size = { 30 } / >
2020-03-08 23:02:28 +00:00
< / N a v L i n k >
2020-08-18 21:49:11 +01:00
< div className = { [ _s . d , _s . flexShrink1 , _s . maxW100PC42PX ] . join ( ' ' ) } >
2020-05-05 06:16:01 +01:00
< div className = { contentClasses } >
2020-05-04 19:44:37 +01:00
< CommentHeader
2020-05-05 06:16:01 +01:00
ancestorAccountId = { ancestorAccountId }
2020-05-04 19:44:37 +01:00
status = { status }
2020-05-07 00:40:54 +01:00
onOpenRevisions = { this . props . onOpenStatusRevisionsPopover }
2020-05-04 19:44:37 +01:00
onOpenLikes = { this . props . onOpenLikes }
onOpenReposts = { this . props . onOpenReposts }
/ >
2020-04-24 04:17:27 +01:00
< StatusContent
status = { status }
onClick = { this . handleClick }
isComment
collapsable
/ >
2020-08-18 21:49:11 +01:00
< div className = { [ _s . d , _s . mt5 ] . join ( ' ' ) } >
2020-05-02 07:25:55 +01:00
< StatusMedia
isComment
status = { status }
onOpenMedia = { this . props . onOpenMedia }
cacheWidth = { this . props . cacheMediaWidth }
defaultWidth = { this . props . cachedMediaWidth }
visible = { this . state . showMedia }
onToggleVisibility = { this . handleToggleMediaVisibility }
width = { this . props . cachedMediaWidth }
/ >
< / d i v >
2020-03-08 23:02:28 +00:00
< / d i v >
2020-08-18 21:49:11 +01:00
< div className = { [ _s . d , _s . flexRow , _s . mt5 ] . join ( ' ' ) } >
2020-05-02 07:25:55 +01:00
< CommentButton
2021-01-25 21:01:55 +00:00
title = { intl . formatMessage ( status . get ( 'favourited' ) && ! ! me ? messages . unlike : messages . like ) }
2020-05-02 07:25:55 +01:00
onClick = { this . handleOnFavorite }
/ >
< CommentButton
title = { intl . formatMessage ( messages . reply ) }
onClick = { this . handleOnReply }
/ >
2020-12-09 20:02:43 +00:00
< CommentButton
2021-01-25 21:01:55 +00:00
title = { intl . formatMessage ( status . get ( 'reblogged' ) && ! ! me ? messages . unrepost : messages . repost ) }
2020-12-09 20:02:43 +00:00
onClick = { this . handleOnRepost }
/ >
2020-05-07 00:40:54 +01:00
< div ref = { this . setMoreNode } >
< CommentButton
title = '···'
onClick = { this . handleOnOpenStatusOptions }
/ >
< / d i v >
2020-03-08 23:02:28 +00:00
< / d i v >
2020-04-24 04:17:27 +01:00
2020-03-08 23:02:28 +00:00
< / d i v >
< / d i v >
2020-03-04 03:45:16 +00:00
2020-03-08 23:02:28 +00:00
< / d i v >
2020-03-04 03:45:16 +00:00
< / d i v >
)
}
}
2020-04-24 04:17:27 +01:00
2020-08-17 21:07:16 +01:00
class CommentButton extends React . PureComponent {
2020-04-24 04:17:27 +01:00
render ( ) {
const { onClick , title } = this . props
return (
< Button
isText
radiusSmall
backgroundColor = 'none'
color = 'tertiary'
2020-04-29 23:32:49 +01:00
className = { [ _s . px5 , _s . bgSubtle _onHover , _s . py2 , _s . mr5 ] . join ( ' ' ) }
2020-04-24 04:17:27 +01:00
onClick = { onClick }
>
< Text size = 'extraSmall' color = 'inherit' weight = 'bold' >
{ title }
< / T e x t >
< / B u t t o n >
)
}
2020-08-18 01:57:35 +01:00
}
2020-08-19 01:22:15 +01:00
CommentButton . propTypes = {
onClick : PropTypes . func . isRequired ,
title : PropTypes . string . isRequired ,
}
2020-08-18 01:57:35 +01:00
const messages = defineMessages ( {
reply : { id : 'status.reply' , defaultMessage : 'Reply' } ,
2020-12-09 20:02:43 +00:00
repost : { id : 'status.repost' , defaultMessage : 'Repost' } ,
unrepost : { id : 'status.unrepost' , defaultMessage : 'Unrepost' } ,
2020-08-18 01:57:35 +01:00
like : { id : 'status.like' , defaultMessage : 'Like' } ,
unlike : { id : 'status.unlike' , defaultMessage : 'Unlike' } ,
} )
const makeMapStateToProps = ( state , props ) => ( {
status : makeGetStatus ( ) ( state , props )
} )
const mapDispatchToProps = ( dispatch ) => ( {
onReply ( status , router ) {
if ( ! me ) return dispatch ( openModal ( 'UNAUTHORIZED' ) )
dispatch ( ( _ , getState ) => {
const state = getState ( ) ;
if ( state . getIn ( [ 'compose' , 'text' ] ) . trim ( ) . length !== 0 ) {
dispatch ( openModal ( 'CONFIRM' , {
message : < FormattedMessage id = 'confirmations.reply.message' defaultMessage = 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' / > ,
confirm : < FormattedMessage id = 'confirmations.reply.confirm' defaultMessage = 'Reply' / > ,
onConfirm : ( ) => dispatch ( replyCompose ( status , router ) ) ,
} ) )
} else {
dispatch ( replyCompose ( status , router , true ) )
}
} )
} ,
onFavorite ( status ) {
if ( ! me ) return dispatch ( openModal ( 'UNAUTHORIZED' ) )
if ( status . get ( 'favourited' ) ) {
dispatch ( unfavorite ( status ) )
} else {
dispatch ( favorite ( status ) )
}
} ,
2020-12-09 20:02:43 +00:00
onRepost ( status ) {
if ( ! me ) return dispatch ( openModal ( 'UNAUTHORIZED' ) )
const alreadyReposted = status . get ( 'reblogged' )
if ( boostModal && ! alreadyReposted ) {
dispatch ( openModal ( MODAL _BOOST , {
status ,
onRepost : ( ) => dispatch ( repost ( status ) ) ,
} ) )
} else {
if ( alreadyReposted ) {
dispatch ( unrepost ( status ) )
} else {
dispatch ( repost ( status ) )
}
}
} ,
2020-08-18 01:57:35 +01:00
onOpenStatusOptions ( targetRef , status ) {
dispatch ( openPopover ( 'STATUS_OPTIONS' , {
targetRef ,
2020-11-25 21:22:37 +00:00
statusId : status . get ( 'id' ) ,
2020-08-18 01:57:35 +01:00
position : 'top' ,
} ) )
} ,
onOpenLikes ( status ) {
dispatch ( openModal ( 'STATUS_LIKES' , { status } ) )
} ,
onOpenReposts ( status ) {
dispatch ( openModal ( 'STATUS_REPOSTS' , { status } ) )
} ,
onOpenStatusRevisionsPopover ( status ) {
dispatch ( openModal ( 'STATUS_REVISIONS' , {
status ,
} ) )
} ,
onOpenMedia ( media , index ) {
dispatch ( openModal ( 'MEDIA' , { media , index } ) ) ;
} ,
} )
Comment . propTypes = {
indent : PropTypes . number ,
intl : PropTypes . object . isRequired ,
ancestorAccountId : PropTypes . string . isRequired ,
status : ImmutablePropTypes . map . isRequired ,
isHidden : PropTypes . bool ,
2020-12-10 06:18:19 +00:00
isDetached : PropTypes . bool ,
2020-08-18 01:57:35 +01:00
isIntersecting : PropTypes . bool ,
isHighlighted : PropTypes . bool ,
onReply : PropTypes . func . isRequired ,
onFavorite : PropTypes . func . isRequired ,
2020-12-09 20:02:43 +00:00
onRepost : PropTypes . func . isRequired ,
2020-08-18 01:57:35 +01:00
onOpenStatusOptions : PropTypes . func . isRequired ,
onOpenLikes : PropTypes . func . isRequired ,
onOpenReposts : PropTypes . func . isRequired ,
onOpenStatusRevisionsPopover : PropTypes . func . isRequired ,
onOpenMedia : PropTypes . func . isRequired
}
export default injectIntl ( connect ( makeMapStateToProps , mapDispatchToProps ) ( Comment ) )