1 import React
from 'react';
2 import ImmutablePropTypes
from 'react-immutable-proptypes';
3 import PropTypes
from 'prop-types';
4 import Avatar
from './avatar';
5 import AvatarOverlay
from './avatar_overlay';
6 import RelativeTimestamp
from './relative_timestamp';
7 import DisplayName
from './display_name';
8 import StatusContent
from './status_content';
9 import StatusActionBar
from './status_action_bar';
10 import { FormattedMessage
} from 'react-intl';
11 import ImmutablePureComponent
from 'react-immutable-pure-component';
12 import { MediaGallery
, Video
} from '../features/ui/util/async-components';
13 import { HotKeys
} from 'react-hotkeys';
14 import classNames
from 'classnames';
16 // We use the component (and not the container) since we do not want
17 // to use the progress bar to show download progress
18 import Bundle
from '../features/ui/components/bundle';
20 export default class Status
extends ImmutablePureComponent
{
22 static contextTypes
= {
23 router: PropTypes
.object
,
27 status: ImmutablePropTypes
.map
,
28 account: ImmutablePropTypes
.map
,
29 onReply: PropTypes
.func
,
30 onFavourite: PropTypes
.func
,
31 onReblog: PropTypes
.func
,
32 onDelete: PropTypes
.func
,
33 onPin: PropTypes
.func
,
34 onOpenMedia: PropTypes
.func
,
35 onOpenVideo: PropTypes
.func
,
36 onBlock: PropTypes
.func
,
37 onEmbed: PropTypes
.func
,
38 onHeightChange: PropTypes
.func
,
39 muted: PropTypes
.bool
,
40 hidden: PropTypes
.bool
,
41 onMoveUp: PropTypes
.func
,
42 onMoveDown: PropTypes
.func
,
49 // Avoid checking props that are functions (and whose equality will always
50 // evaluate to false. See react-immutable-pure-component for usage.
58 updateOnStates
= ['isExpanded']
61 if (!this.context
.router
) {
65 const { status
} = this.props
;
66 this.context
.router
.history
.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
69 handleAccountClick
= (e
) => {
70 if (this.context
.router
&& e
.button
=== 0) {
71 const id
= e
.currentTarget
.getAttribute('data-id');
73 this.context
.router
.history
.push(`/accounts/${id}`);
77 handleExpandedToggle
= () => {
78 this.setState({ isExpanded: !this.state
.isExpanded
});
81 renderLoadingMediaGallery () {
82 return <div className
='media_gallery' style
={{ height: '110px' }} />;
85 renderLoadingVideoPlayer () {
86 return <div className
='media-spoiler-video' style
={{ height: '110px' }} />;
89 handleOpenVideo
= startTime
=> {
90 this.props
.onOpenVideo(this._properStatus().getIn(['media_attachments', 0]), startTime
);
93 handleHotkeyReply
= e
=> {
95 this.props
.onReply(this._properStatus(), this.context
.router
.history
);
98 handleHotkeyFavourite
= () => {
99 this.props
.onFavourite(this._properStatus());
102 handleHotkeyBoost
= e
=> {
103 this.props
.onReblog(this._properStatus(), e
);
106 handleHotkeyMention
= e
=> {
108 this.props
.onMention(this._properStatus().get('account'), this.context
.router
.history
);
111 handleHotkeyOpen
= () => {
112 this.context
.router
.history
.push(`/statuses/${this._properStatus().get('id')}`);
115 handleHotkeyOpenProfile
= () => {
116 this.context
.router
.history
.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`);
119 handleHotkeyMoveUp
= () => {
120 this.props
.onMoveUp(this.props
.status
.get('id'));
123 handleHotkeyMoveDown
= () => {
124 this.props
.onMoveDown(this.props
.status
.get('id'));
128 const { status
} = this.props
;
130 if (status
.get('reblog', null) !== null && typeof status
.get('reblog') === 'object') {
131 return status
.get('reblog');
139 let statusAvatar
, prepend
;
141 const { hidden
} = this.props
;
142 const { isExpanded
} = this.state
;
144 let { status
, account
, ...other
} = this.props
;
146 if (status
=== null) {
153 {status
.getIn(['account', 'display_name']) || status
.getIn(['account', 'username'])}
154 {status
.get('content')}
159 if (status
.get('reblog', null) !== null && typeof status
.get('reblog') === 'object') {
160 const display_name_html
= { __html: status
.getIn(['account', 'display_name_html']) };
163 <div className
='status__prepend'>
164 <div className
='status__prepend-icon-wrapper'><i className
='fa fa-fw fa-retweet status__prepend-icon' /></div>
165 <FormattedMessage id
='status.reblogged_by' defaultMessage
='{name} boosted' values
={{ name: <a onClick
={this.handleAccountClick
} data
-id
={status
.getIn(['account', 'id'])} href
={status
.getIn(['account', 'url'])} className
='status__display-name muted'><bdi
><strong dangerouslySetInnerHTML
={display_name_html
} /></bdi
></a
> }} />
169 account
= status
.get('account');
170 status
= status
.get('reblog');
173 if (status
.get('media_attachments').size
> 0 && !this.props
.muted
) {
174 if (status
.get('media_attachments').some(item
=> item
.get('type') === 'unknown')) {
176 } else if (status
.getIn(['media_attachments', 0, 'type']) === 'video') {
177 const video
= status
.getIn(['media_attachments', 0]);
180 <Bundle fetchComponent
={Video
} loading
={this.renderLoadingVideoPlayer
} >
183 preview
={video
.get('preview_url')}
184 src
={video
.get('url')}
187 sensitive
={status
.get('sensitive')}
188 onOpenVideo
={this.handleOpenVideo
}
195 <Bundle fetchComponent
={MediaGallery
} loading
={this.renderLoadingMediaGallery
} >
196 {Component
=> <Component media
={status
.get('media_attachments')} sensitive
={status
.get('sensitive')} height
={110} onOpenMedia
={this.props
.onOpenMedia
} />}
202 if (account
=== undefined || account
=== null) {
203 statusAvatar
= <Avatar account
={status
.get('account')} size
={48} />;
205 statusAvatar
= <AvatarOverlay account
={status
.get('account')} friend
={account
} />;
208 const handlers
= this.props
.muted
? {} : {
209 reply: this.handleHotkeyReply
,
210 favourite: this.handleHotkeyFavourite
,
211 boost: this.handleHotkeyBoost
,
212 mention: this.handleHotkeyMention
,
213 open: this.handleHotkeyOpen
,
214 openProfile: this.handleHotkeyOpenProfile
,
215 moveUp: this.handleHotkeyMoveUp
,
216 moveDown: this.handleHotkeyMoveDown
,
220 <HotKeys handlers
={handlers
}>
221 <div className
={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { focusable: !this.props
.muted
})} tabIndex
={this.props
.muted
? null : 0}>
224 <div className
={classNames('status', `status-${status.get('visibility')}`, { muted: this.props
.muted
})} data
-id
={status
.get('id')}>
225 <div className
='status__info'>
226 <a href
={status
.get('url')} className
='status__relative-time' target
='_blank' rel
='noopener'><RelativeTimestamp timestamp
={status
.get('created_at')} /></a>
228 <a onClick
={this.handleAccountClick
} target
='_blank' data
-id
={status
.getIn(['account', 'id'])} href
={status
.getIn(['account', 'url'])} title
={status
.getIn(['account', 'acct'])} className
='status__display-name'>
229 <div className
='status__avatar'>
233 <DisplayName account
={status
.get('account')} />
237 <StatusContent status
={status
} onClick
={this.handleClick
} expanded
={isExpanded
} onExpandedToggle
={this.handleExpandedToggle
} />
241 <StatusActionBar status
={status
} account
={account
} {...other
} />