account_card.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import React from 'react';
  2. import ImmutablePureComponent from 'react-immutable-pure-component';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import PropTypes from 'prop-types';
  5. import { connect } from 'react-redux';
  6. import { makeGetAccount } from 'mastodon/selectors';
  7. import Avatar from 'mastodon/components/avatar';
  8. import DisplayName from 'mastodon/components/display_name';
  9. import Permalink from 'mastodon/components/permalink';
  10. import RelativeTimestamp from 'mastodon/components/relative_timestamp';
  11. import IconButton from 'mastodon/components/icon_button';
  12. import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
  13. import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
  14. import ShortNumber from 'mastodon/components/short_number';
  15. import {
  16. followAccount,
  17. unfollowAccount,
  18. blockAccount,
  19. unblockAccount,
  20. unmuteAccount,
  21. } from 'mastodon/actions/accounts';
  22. import { openModal } from 'mastodon/actions/modal';
  23. import { initMuteModal } from 'mastodon/actions/mutes';
  24. const messages = defineMessages({
  25. follow: { id: 'account.follow', defaultMessage: 'Follow' },
  26. unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
  27. requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
  28. unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
  29. unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
  30. unfollowConfirm: {
  31. id: 'confirmations.unfollow.confirm',
  32. defaultMessage: 'Unfollow',
  33. },
  34. });
  35. const makeMapStateToProps = () => {
  36. const getAccount = makeGetAccount();
  37. const mapStateToProps = (state, { id }) => ({
  38. account: getAccount(state, id),
  39. });
  40. return mapStateToProps;
  41. };
  42. const mapDispatchToProps = (dispatch, { intl }) => ({
  43. onFollow(account) {
  44. if (
  45. account.getIn(['relationship', 'following']) ||
  46. account.getIn(['relationship', 'requested'])
  47. ) {
  48. if (unfollowModal) {
  49. dispatch(
  50. openModal('CONFIRM', {
  51. message: (
  52. <FormattedMessage
  53. id='confirmations.unfollow.message'
  54. defaultMessage='Are you sure you want to unfollow {name}?'
  55. values={{ name: <strong>@{account.get('acct')}</strong> }}
  56. />
  57. ),
  58. confirm: intl.formatMessage(messages.unfollowConfirm),
  59. onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
  60. }),
  61. );
  62. } else {
  63. dispatch(unfollowAccount(account.get('id')));
  64. }
  65. } else {
  66. dispatch(followAccount(account.get('id')));
  67. }
  68. },
  69. onBlock(account) {
  70. if (account.getIn(['relationship', 'blocking'])) {
  71. dispatch(unblockAccount(account.get('id')));
  72. } else {
  73. dispatch(blockAccount(account.get('id')));
  74. }
  75. },
  76. onMute(account) {
  77. if (account.getIn(['relationship', 'muting'])) {
  78. dispatch(unmuteAccount(account.get('id')));
  79. } else {
  80. dispatch(initMuteModal(account));
  81. }
  82. },
  83. });
  84. export default
  85. @injectIntl
  86. @connect(makeMapStateToProps, mapDispatchToProps)
  87. class AccountCard extends ImmutablePureComponent {
  88. static propTypes = {
  89. account: ImmutablePropTypes.map.isRequired,
  90. intl: PropTypes.object.isRequired,
  91. onFollow: PropTypes.func.isRequired,
  92. onBlock: PropTypes.func.isRequired,
  93. onMute: PropTypes.func.isRequired,
  94. };
  95. _updateEmojis() {
  96. const node = this.node;
  97. if (!node || autoPlayGif) {
  98. return;
  99. }
  100. const emojis = node.querySelectorAll('.custom-emoji');
  101. for (var i = 0; i < emojis.length; i++) {
  102. let emoji = emojis[i];
  103. if (emoji.classList.contains('status-emoji')) {
  104. continue;
  105. }
  106. emoji.classList.add('status-emoji');
  107. emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
  108. emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
  109. }
  110. }
  111. componentDidMount() {
  112. this._updateEmojis();
  113. }
  114. componentDidUpdate() {
  115. this._updateEmojis();
  116. }
  117. handleEmojiMouseEnter = ({ target }) => {
  118. target.src = target.getAttribute('data-original');
  119. };
  120. handleEmojiMouseLeave = ({ target }) => {
  121. target.src = target.getAttribute('data-static');
  122. };
  123. handleFollow = () => {
  124. this.props.onFollow(this.props.account);
  125. };
  126. handleBlock = () => {
  127. this.props.onBlock(this.props.account);
  128. };
  129. handleMute = () => {
  130. this.props.onMute(this.props.account);
  131. };
  132. setRef = (c) => {
  133. this.node = c;
  134. };
  135. render() {
  136. const { account, intl } = this.props;
  137. let buttons;
  138. if (
  139. account.get('id') !== me &&
  140. account.get('relationship', null) !== null
  141. ) {
  142. const following = account.getIn(['relationship', 'following']);
  143. const requested = account.getIn(['relationship', 'requested']);
  144. const blocking = account.getIn(['relationship', 'blocking']);
  145. const muting = account.getIn(['relationship', 'muting']);
  146. if (requested) {
  147. buttons = (
  148. <IconButton
  149. disabled
  150. icon='hourglass'
  151. title={intl.formatMessage(messages.requested)}
  152. />
  153. );
  154. } else if (blocking) {
  155. buttons = (
  156. <IconButton
  157. active
  158. icon='unlock'
  159. title={intl.formatMessage(messages.unblock, {
  160. name: account.get('username'),
  161. })}
  162. onClick={this.handleBlock}
  163. />
  164. );
  165. } else if (muting) {
  166. buttons = (
  167. <IconButton
  168. active
  169. icon='volume-up'
  170. title={intl.formatMessage(messages.unmute, {
  171. name: account.get('username'),
  172. })}
  173. onClick={this.handleMute}
  174. />
  175. );
  176. } else if (!account.get('moved') || following) {
  177. buttons = (
  178. <IconButton
  179. icon={following ? 'user-times' : 'user-plus'}
  180. title={intl.formatMessage(
  181. following ? messages.unfollow : messages.follow,
  182. )}
  183. onClick={this.handleFollow}
  184. active={following}
  185. />
  186. );
  187. }
  188. }
  189. return (
  190. <div className='directory__card'>
  191. <div className='directory__card__img'>
  192. <img
  193. src={
  194. autoPlayGif ? account.get('header') : account.get('header_static')
  195. }
  196. alt=''
  197. />
  198. </div>
  199. <div className='directory__card__bar'>
  200. <Permalink
  201. className='directory__card__bar__name'
  202. href={account.get('url')}
  203. to={`/accounts/${account.get('id')}`}
  204. >
  205. <Avatar account={account} size={48} />
  206. <DisplayName account={account} />
  207. </Permalink>
  208. <div className='directory__card__bar__relationship account__relationship'>
  209. {buttons}
  210. </div>
  211. </div>
  212. <div className='directory__card__extra' ref={this.setRef}>
  213. <div
  214. className='account__header__content'
  215. dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
  216. />
  217. </div>
  218. <div className='directory__card__extra'>
  219. <div className='accounts-table__count'>
  220. <ShortNumber value={account.get('statuses_count')} />
  221. <small>
  222. <FormattedMessage id='account.posts' defaultMessage='Toots' />
  223. </small>
  224. </div>
  225. <div className='accounts-table__count'>
  226. <ShortNumber value={account.get('followers_count')} />{' '}
  227. <small>
  228. <FormattedMessage
  229. id='account.followers'
  230. defaultMessage='Followers'
  231. />
  232. </small>
  233. </div>
  234. <div className='accounts-table__count'>
  235. {account.get('last_status_at') === null ? (
  236. <FormattedMessage
  237. id='account.never_active'
  238. defaultMessage='Never'
  239. />
  240. ) : (
  241. <RelativeTimestamp timestamp={account.get('last_status_at')} />
  242. )}{' '}
  243. <small>
  244. <FormattedMessage
  245. id='account.last_status'
  246. defaultMessage='Last active'
  247. />
  248. </small>
  249. </div>
  250. </div>
  251. </div>
  252. );
  253. }
  254. }