You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
7.9 KiB

  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. handleMouseEnter = ({ currentTarget }) => {
  96. if (autoPlayGif) {
  97. return;
  98. }
  99. const emojis = currentTarget.querySelectorAll('.custom-emoji');
  100. for (var i = 0; i < emojis.length; i++) {
  101. let emoji = emojis[i];
  102. emoji.src = emoji.getAttribute('data-original');
  103. }
  104. }
  105. handleMouseLeave = ({ currentTarget }) => {
  106. if (autoPlayGif) {
  107. return;
  108. }
  109. const emojis = currentTarget.querySelectorAll('.custom-emoji');
  110. for (var i = 0; i < emojis.length; i++) {
  111. let emoji = emojis[i];
  112. emoji.src = emoji.getAttribute('data-static');
  113. }
  114. }
  115. handleFollow = () => {
  116. this.props.onFollow(this.props.account);
  117. };
  118. handleBlock = () => {
  119. this.props.onBlock(this.props.account);
  120. };
  121. handleMute = () => {
  122. this.props.onMute(this.props.account);
  123. };
  124. render() {
  125. const { account, intl } = this.props;
  126. let buttons;
  127. if (
  128. account.get('id') !== me &&
  129. account.get('relationship', null) !== null
  130. ) {
  131. const following = account.getIn(['relationship', 'following']);
  132. const requested = account.getIn(['relationship', 'requested']);
  133. const blocking = account.getIn(['relationship', 'blocking']);
  134. const muting = account.getIn(['relationship', 'muting']);
  135. if (requested) {
  136. buttons = (
  137. <IconButton
  138. disabled
  139. icon='hourglass'
  140. title={intl.formatMessage(messages.requested)}
  141. />
  142. );
  143. } else if (blocking) {
  144. buttons = (
  145. <IconButton
  146. active
  147. icon='unlock'
  148. title={intl.formatMessage(messages.unblock, {
  149. name: account.get('username'),
  150. })}
  151. onClick={this.handleBlock}
  152. />
  153. );
  154. } else if (muting) {
  155. buttons = (
  156. <IconButton
  157. active
  158. icon='volume-up'
  159. title={intl.formatMessage(messages.unmute, {
  160. name: account.get('username'),
  161. })}
  162. onClick={this.handleMute}
  163. />
  164. );
  165. } else if (!account.get('moved') || following) {
  166. buttons = (
  167. <IconButton
  168. icon={following ? 'user-times' : 'user-plus'}
  169. title={intl.formatMessage(
  170. following ? messages.unfollow : messages.follow,
  171. )}
  172. onClick={this.handleFollow}
  173. active={following}
  174. />
  175. );
  176. }
  177. }
  178. return (
  179. <div className='directory__card'>
  180. <div className='directory__card__img'>
  181. <img
  182. src={
  183. autoPlayGif ? account.get('header') : account.get('header_static')
  184. }
  185. alt=''
  186. />
  187. </div>
  188. <div className='directory__card__bar'>
  189. <Permalink
  190. className='directory__card__bar__name'
  191. href={account.get('url')}
  192. to={`/accounts/${account.get('id')}`}
  193. >
  194. <Avatar account={account} size={48} />
  195. <DisplayName account={account} />
  196. </Permalink>
  197. <div className='directory__card__bar__relationship account__relationship'>
  198. {buttons}
  199. </div>
  200. </div>
  201. <div className='directory__card__extra' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
  202. <div
  203. className='account__header__content translate'
  204. dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
  205. />
  206. </div>
  207. <div className='directory__card__extra'>
  208. <div className='accounts-table__count'>
  209. <ShortNumber value={account.get('statuses_count')} />
  210. <small>
  211. <FormattedMessage id='account.posts' defaultMessage='Toots' />
  212. </small>
  213. </div>
  214. <div className='accounts-table__count'>
  215. <ShortNumber value={account.get('followers_count')} />{' '}
  216. <small>
  217. <FormattedMessage
  218. id='account.followers'
  219. defaultMessage='Followers'
  220. />
  221. </small>
  222. </div>
  223. <div className='accounts-table__count'>
  224. {account.get('last_status_at') === null ? (
  225. <FormattedMessage
  226. id='account.never_active'
  227. defaultMessage='Never'
  228. />
  229. ) : (
  230. <RelativeTimestamp timestamp={account.get('last_status_at')} />
  231. )}{' '}
  232. <small>
  233. <FormattedMessage
  234. id='account.last_status'
  235. defaultMessage='Last active'
  236. />
  237. </small>
  238. </div>
  239. </div>
  240. </div>
  241. );
  242. }
  243. }