]> cat aescling's git repositories - mastodon.git/blob - app/javascript/mastodon/features/notifications/components/notification.js
Change routing paths to use usernames in web UI (#16171)
[mastodon.git] / app / javascript / mastodon / features / notifications / components / notification.js
1 import React from 'react';
2 import ImmutablePropTypes from 'react-immutable-proptypes';
3 import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
4 import { HotKeys } from 'react-hotkeys';
5 import PropTypes from 'prop-types';
6 import ImmutablePureComponent from 'react-immutable-pure-component';
7 import { me } from 'mastodon/initial_state';
8 import StatusContainer from 'mastodon/containers/status_container';
9 import AccountContainer from 'mastodon/containers/account_container';
10 import FollowRequestContainer from '../containers/follow_request_container';
11 import Icon from 'mastodon/components/icon';
12 import Permalink from 'mastodon/components/permalink';
13 import classNames from 'classnames';
14
15 const messages = defineMessages({
16 favourite: { id: 'notification.favourite', defaultMessage: '{name} favourited your status' },
17 follow: { id: 'notification.follow', defaultMessage: '{name} followed you' },
18 ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' },
19 poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
20 reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your status' },
21 status: { id: 'notification.status', defaultMessage: '{name} just posted' },
22 });
23
24 const notificationForScreenReader = (intl, message, timestamp) => {
25 const output = [message];
26
27 output.push(intl.formatDate(timestamp, { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }));
28
29 return output.join(', ');
30 };
31
32 export default @injectIntl
33 class Notification extends ImmutablePureComponent {
34
35 static contextTypes = {
36 router: PropTypes.object,
37 };
38
39 static propTypes = {
40 notification: ImmutablePropTypes.map.isRequired,
41 hidden: PropTypes.bool,
42 onMoveUp: PropTypes.func.isRequired,
43 onMoveDown: PropTypes.func.isRequired,
44 onMention: PropTypes.func.isRequired,
45 onFavourite: PropTypes.func.isRequired,
46 onReblog: PropTypes.func.isRequired,
47 onToggleHidden: PropTypes.func.isRequired,
48 status: ImmutablePropTypes.map,
49 intl: PropTypes.object.isRequired,
50 getScrollPosition: PropTypes.func,
51 updateScrollBottom: PropTypes.func,
52 cacheMediaWidth: PropTypes.func,
53 cachedMediaWidth: PropTypes.number,
54 unread: PropTypes.bool,
55 };
56
57 handleMoveUp = () => {
58 const { notification, onMoveUp } = this.props;
59 onMoveUp(notification.get('id'));
60 }
61
62 handleMoveDown = () => {
63 const { notification, onMoveDown } = this.props;
64 onMoveDown(notification.get('id'));
65 }
66
67 handleOpen = () => {
68 const { notification } = this.props;
69
70 if (notification.get('status')) {
71 this.context.router.history.push(`/@${notification.getIn(['status', 'account', 'acct'])}/${notification.get('status')}`);
72 } else {
73 this.handleOpenProfile();
74 }
75 }
76
77 handleOpenProfile = () => {
78 const { notification } = this.props;
79 this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`);
80 }
81
82 handleMention = e => {
83 e.preventDefault();
84
85 const { notification, onMention } = this.props;
86 onMention(notification.get('account'), this.context.router.history);
87 }
88
89 handleHotkeyFavourite = () => {
90 const { status } = this.props;
91 if (status) this.props.onFavourite(status);
92 }
93
94 handleHotkeyBoost = e => {
95 const { status } = this.props;
96 if (status) this.props.onReblog(status, e);
97 }
98
99 handleHotkeyToggleHidden = () => {
100 const { status } = this.props;
101 if (status) this.props.onToggleHidden(status);
102 }
103
104 getHandlers () {
105 return {
106 reply: this.handleMention,
107 favourite: this.handleHotkeyFavourite,
108 boost: this.handleHotkeyBoost,
109 mention: this.handleMention,
110 open: this.handleOpen,
111 openProfile: this.handleOpenProfile,
112 moveUp: this.handleMoveUp,
113 moveDown: this.handleMoveDown,
114 toggleHidden: this.handleHotkeyToggleHidden,
115 };
116 }
117
118 renderFollow (notification, account, link) {
119 const { intl, unread } = this.props;
120
121 return (
122 <HotKeys handlers={this.getHandlers()}>
123 <div className={classNames('notification notification-follow focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.follow, { name: account.get('acct') }), notification.get('created_at'))}>
124 <div className='notification__message'>
125 <div className='notification__favourite-icon-wrapper'>
126 <Icon id='user-plus' fixedWidth />
127 </div>
128
129 <span title={notification.get('created_at')}>
130 <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} />
131 </span>
132 </div>
133
134 <AccountContainer id={account.get('id')} hidden={this.props.hidden} />
135 </div>
136 </HotKeys>
137 );
138 }
139
140 renderFollowRequest (notification, account, link) {
141 const { intl, unread } = this.props;
142
143 return (
144 <HotKeys handlers={this.getHandlers()}>
145 <div className={classNames('notification notification-follow-request focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage({ id: 'notification.follow_request', defaultMessage: '{name} has requested to follow you' }, { name: account.get('acct') }), notification.get('created_at'))}>
146 <div className='notification__message'>
147 <div className='notification__favourite-icon-wrapper'>
148 <Icon id='user' fixedWidth />
149 </div>
150
151 <span title={notification.get('created_at')}>
152 <FormattedMessage id='notification.follow_request' defaultMessage='{name} has requested to follow you' values={{ name: link }} />
153 </span>
154 </div>
155
156 <FollowRequestContainer id={account.get('id')} withNote={false} hidden={this.props.hidden} />
157 </div>
158 </HotKeys>
159 );
160 }
161
162 renderMention (notification) {
163 return (
164 <StatusContainer
165 id={notification.get('status')}
166 withDismiss
167 hidden={this.props.hidden}
168 onMoveDown={this.handleMoveDown}
169 onMoveUp={this.handleMoveUp}
170 contextType='notifications'
171 getScrollPosition={this.props.getScrollPosition}
172 updateScrollBottom={this.props.updateScrollBottom}
173 cachedMediaWidth={this.props.cachedMediaWidth}
174 cacheMediaWidth={this.props.cacheMediaWidth}
175 unread={this.props.unread}
176 />
177 );
178 }
179
180 renderFavourite (notification, link) {
181 const { intl, unread } = this.props;
182
183 return (
184 <HotKeys handlers={this.getHandlers()}>
185 <div className={classNames('notification notification-favourite focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.favourite, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
186 <div className='notification__message'>
187 <div className='notification__favourite-icon-wrapper'>
188 <Icon id='star' className='star-icon' fixedWidth />
189 </div>
190
191 <span title={notification.get('created_at')}>
192 <FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
193 </span>
194 </div>
195
196 <StatusContainer
197 id={notification.get('status')}
198 account={notification.get('account')}
199 muted
200 withDismiss
201 hidden={!!this.props.hidden}
202 getScrollPosition={this.props.getScrollPosition}
203 updateScrollBottom={this.props.updateScrollBottom}
204 cachedMediaWidth={this.props.cachedMediaWidth}
205 cacheMediaWidth={this.props.cacheMediaWidth}
206 />
207 </div>
208 </HotKeys>
209 );
210 }
211
212 renderReblog (notification, link) {
213 const { intl, unread } = this.props;
214
215 return (
216 <HotKeys handlers={this.getHandlers()}>
217 <div className={classNames('notification notification-reblog focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.reblog, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
218 <div className='notification__message'>
219 <div className='notification__favourite-icon-wrapper'>
220 <Icon id='retweet' fixedWidth />
221 </div>
222
223 <span title={notification.get('created_at')}>
224 <FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
225 </span>
226 </div>
227
228 <StatusContainer
229 id={notification.get('status')}
230 account={notification.get('account')}
231 muted
232 withDismiss
233 hidden={this.props.hidden}
234 getScrollPosition={this.props.getScrollPosition}
235 updateScrollBottom={this.props.updateScrollBottom}
236 cachedMediaWidth={this.props.cachedMediaWidth}
237 cacheMediaWidth={this.props.cacheMediaWidth}
238 />
239 </div>
240 </HotKeys>
241 );
242 }
243
244 renderStatus (notification, link) {
245 const { intl, unread } = this.props;
246
247 return (
248 <HotKeys handlers={this.getHandlers()}>
249 <div className={classNames('notification notification-status focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.status, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
250 <div className='notification__message'>
251 <div className='notification__favourite-icon-wrapper'>
252 <Icon id='home' fixedWidth />
253 </div>
254
255 <span title={notification.get('created_at')}>
256 <FormattedMessage id='notification.status' defaultMessage='{name} just posted' values={{ name: link }} />
257 </span>
258 </div>
259
260 <StatusContainer
261 id={notification.get('status')}
262 account={notification.get('account')}
263 muted
264 withDismiss
265 hidden={this.props.hidden}
266 getScrollPosition={this.props.getScrollPosition}
267 updateScrollBottom={this.props.updateScrollBottom}
268 cachedMediaWidth={this.props.cachedMediaWidth}
269 cacheMediaWidth={this.props.cacheMediaWidth}
270 />
271 </div>
272 </HotKeys>
273 );
274 }
275
276 renderPoll (notification, account) {
277 const { intl, unread } = this.props;
278 const ownPoll = me === account.get('id');
279 const message = ownPoll ? intl.formatMessage(messages.ownPoll) : intl.formatMessage(messages.poll);
280
281 return (
282 <HotKeys handlers={this.getHandlers()}>
283 <div className={classNames('notification notification-poll focusable', { unread })} tabIndex='0' aria-label={notificationForScreenReader(intl, message, notification.get('created_at'))}>
284 <div className='notification__message'>
285 <div className='notification__favourite-icon-wrapper'>
286 <Icon id='tasks' fixedWidth />
287 </div>
288
289 <span title={notification.get('created_at')}>
290 {ownPoll ? (
291 <FormattedMessage id='notification.own_poll' defaultMessage='Your poll has ended' />
292 ) : (
293 <FormattedMessage id='notification.poll' defaultMessage='A poll you have voted in has ended' />
294 )}
295 </span>
296 </div>
297
298 <StatusContainer
299 id={notification.get('status')}
300 account={account}
301 muted
302 withDismiss
303 hidden={this.props.hidden}
304 getScrollPosition={this.props.getScrollPosition}
305 updateScrollBottom={this.props.updateScrollBottom}
306 cachedMediaWidth={this.props.cachedMediaWidth}
307 cacheMediaWidth={this.props.cacheMediaWidth}
308 />
309 </div>
310 </HotKeys>
311 );
312 }
313
314 render () {
315 const { notification } = this.props;
316 const account = notification.get('account');
317 const displayNameHtml = { __html: account.get('display_name_html') };
318 const link = <bdi><Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} /></bdi>;
319
320 switch(notification.get('type')) {
321 case 'follow':
322 return this.renderFollow(notification, account, link);
323 case 'follow_request':
324 return this.renderFollowRequest(notification, account, link);
325 case 'mention':
326 return this.renderMention(notification);
327 case 'favourite':
328 return this.renderFavourite(notification, link);
329 case 'reblog':
330 return this.renderReblog(notification, link);
331 case 'status':
332 return this.renderStatus(notification, link);
333 case 'poll':
334 return this.renderPoll(notification, account);
335 }
336
337 return null;
338 }
339
340 }
This page took 0.155801 seconds and 4 git commands to generate.