]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/components/status.js
Upgrade ESLint to version 4.x (#6276)
[mastodon.git] / app / javascript / mastodon / components / status.js
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';
15
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';
19
20 export default class Status extends ImmutablePureComponent {
21
22 static contextTypes = {
23 router: PropTypes.object,
24 };
25
26 static propTypes = {
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,
43 };
44
45 state = {
46 isExpanded: false,
47 }
48
49 // Avoid checking props that are functions (and whose equality will always
50 // evaluate to false. See react-immutable-pure-component for usage.
51 updateOnProps = [
52 'status',
53 'account',
54 'muted',
55 'hidden',
56 ]
57
58 updateOnStates = ['isExpanded']
59
60 handleClick = () => {
61 if (!this.context.router) {
62 return;
63 }
64
65 const { status } = this.props;
66 this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
67 }
68
69 handleAccountClick = (e) => {
70 if (this.context.router && e.button === 0) {
71 const id = e.currentTarget.getAttribute('data-id');
72 e.preventDefault();
73 this.context.router.history.push(`/accounts/${id}`);
74 }
75 }
76
77 handleExpandedToggle = () => {
78 this.setState({ isExpanded: !this.state.isExpanded });
79 };
80
81 renderLoadingMediaGallery () {
82 return <div className='media_gallery' style={{ height: '110px' }} />;
83 }
84
85 renderLoadingVideoPlayer () {
86 return <div className='media-spoiler-video' style={{ height: '110px' }} />;
87 }
88
89 handleOpenVideo = startTime => {
90 this.props.onOpenVideo(this._properStatus().getIn(['media_attachments', 0]), startTime);
91 }
92
93 handleHotkeyReply = e => {
94 e.preventDefault();
95 this.props.onReply(this._properStatus(), this.context.router.history);
96 }
97
98 handleHotkeyFavourite = () => {
99 this.props.onFavourite(this._properStatus());
100 }
101
102 handleHotkeyBoost = e => {
103 this.props.onReblog(this._properStatus(), e);
104 }
105
106 handleHotkeyMention = e => {
107 e.preventDefault();
108 this.props.onMention(this._properStatus().get('account'), this.context.router.history);
109 }
110
111 handleHotkeyOpen = () => {
112 this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`);
113 }
114
115 handleHotkeyOpenProfile = () => {
116 this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`);
117 }
118
119 handleHotkeyMoveUp = () => {
120 this.props.onMoveUp(this.props.status.get('id'));
121 }
122
123 handleHotkeyMoveDown = () => {
124 this.props.onMoveDown(this.props.status.get('id'));
125 }
126
127 _properStatus () {
128 const { status } = this.props;
129
130 if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
131 return status.get('reblog');
132 } else {
133 return status;
134 }
135 }
136
137 render () {
138 let media = null;
139 let statusAvatar, prepend;
140
141 const { hidden } = this.props;
142 const { isExpanded } = this.state;
143
144 let { status, account, ...other } = this.props;
145
146 if (status === null) {
147 return null;
148 }
149
150 if (hidden) {
151 return (
152 <div>
153 {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
154 {status.get('content')}
155 </div>
156 );
157 }
158
159 if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
160 const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
161
162 prepend = (
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> }} />
166 </div>
167 );
168
169 account = status.get('account');
170 status = status.get('reblog');
171 }
172
173 if (status.get('media_attachments').size > 0 && !this.props.muted) {
174 if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
175
176 } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
177 const video = status.getIn(['media_attachments', 0]);
178
179 media = (
180 <Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
181 {Component => (
182 <Component
183 preview={video.get('preview_url')}
184 src={video.get('url')}
185 width={239}
186 height={110}
187 sensitive={status.get('sensitive')}
188 onOpenVideo={this.handleOpenVideo}
189 />
190 )}
191 </Bundle>
192 );
193 } else {
194 media = (
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} />}
197 </Bundle>
198 );
199 }
200 }
201
202 if (account === undefined || account === null) {
203 statusAvatar = <Avatar account={status.get('account')} size={48} />;
204 }else{
205 statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
206 }
207
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,
217 };
218
219 return (
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}>
222 {prepend}
223
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>
227
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'>
230 {statusAvatar}
231 </div>
232
233 <DisplayName account={status.get('account')} />
234 </a>
235 </div>
236
237 <StatusContent status={status} onClick={this.handleClick} expanded={isExpanded} onExpandedToggle={this.handleExpandedToggle} />
238
239 {media}
240
241 <StatusActionBar status={status} account={account} {...other} />
242 </div>
243 </div>
244 </HotKeys>
245 );
246 }
247
248 }
This page took 0.142752 seconds and 4 git commands to generate.