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.

234 lines
8.2 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 { Link } from 'react-router-dom';
  10. import Button from 'mastodon/components/button';
  11. import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
  12. import { autoPlayGif, me, unfollowModal } from 'mastodon/initial_state';
  13. import ShortNumber from 'mastodon/components/short_number';
  14. import {
  15. followAccount,
  16. unfollowAccount,
  17. unblockAccount,
  18. unmuteAccount,
  19. } from 'mastodon/actions/accounts';
  20. import { openModal } from 'mastodon/actions/modal';
  21. import classNames from 'classnames';
  22. const messages = defineMessages({
  23. unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
  24. follow: { id: 'account.follow', defaultMessage: 'Follow' },
  25. cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
  26. cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' },
  27. requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
  28. unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
  29. unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
  30. unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
  31. edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
  32. });
  33. const makeMapStateToProps = () => {
  34. const getAccount = makeGetAccount();
  35. const mapStateToProps = (state, { id }) => ({
  36. account: getAccount(state, id),
  37. });
  38. return mapStateToProps;
  39. };
  40. const mapDispatchToProps = (dispatch, { intl }) => ({
  41. onFollow(account) {
  42. if (account.getIn(['relationship', 'following'])) {
  43. if (unfollowModal) {
  44. dispatch(
  45. openModal('CONFIRM', {
  46. message: (
  47. <FormattedMessage
  48. id='confirmations.unfollow.message'
  49. defaultMessage='Are you sure you want to unfollow {name}?'
  50. values={{ name: <strong>@{account.get('acct')}</strong> }}
  51. />
  52. ),
  53. confirm: intl.formatMessage(messages.unfollowConfirm),
  54. onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
  55. }),
  56. );
  57. } else {
  58. dispatch(unfollowAccount(account.get('id')));
  59. }
  60. } else if (account.getIn(['relationship', 'requested'])) {
  61. if (unfollowModal) {
  62. dispatch(openModal('CONFIRM', {
  63. message: <FormattedMessage id='confirmations.cancel_follow_request.message' defaultMessage='Are you sure you want to withdraw your request to follow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
  64. confirm: intl.formatMessage(messages.cancelFollowRequestConfirm),
  65. onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
  66. }));
  67. } else {
  68. dispatch(unfollowAccount(account.get('id')));
  69. }
  70. } else {
  71. dispatch(followAccount(account.get('id')));
  72. }
  73. },
  74. onBlock(account) {
  75. if (account.getIn(['relationship', 'blocking'])) {
  76. dispatch(unblockAccount(account.get('id')));
  77. }
  78. },
  79. onMute(account) {
  80. if (account.getIn(['relationship', 'muting'])) {
  81. dispatch(unmuteAccount(account.get('id')));
  82. }
  83. },
  84. });
  85. class AccountCard extends ImmutablePureComponent {
  86. static propTypes = {
  87. account: ImmutablePropTypes.map.isRequired,
  88. intl: PropTypes.object.isRequired,
  89. onFollow: PropTypes.func.isRequired,
  90. onBlock: PropTypes.func.isRequired,
  91. onMute: PropTypes.func.isRequired,
  92. };
  93. handleMouseEnter = ({ currentTarget }) => {
  94. if (autoPlayGif) {
  95. return;
  96. }
  97. const emojis = currentTarget.querySelectorAll('.custom-emoji');
  98. for (var i = 0; i < emojis.length; i++) {
  99. let emoji = emojis[i];
  100. emoji.src = emoji.getAttribute('data-original');
  101. }
  102. };
  103. handleMouseLeave = ({ currentTarget }) => {
  104. if (autoPlayGif) {
  105. return;
  106. }
  107. const emojis = currentTarget.querySelectorAll('.custom-emoji');
  108. for (var i = 0; i < emojis.length; i++) {
  109. let emoji = emojis[i];
  110. emoji.src = emoji.getAttribute('data-static');
  111. }
  112. };
  113. handleFollow = () => {
  114. this.props.onFollow(this.props.account);
  115. };
  116. handleBlock = () => {
  117. this.props.onBlock(this.props.account);
  118. };
  119. handleMute = () => {
  120. this.props.onMute(this.props.account);
  121. };
  122. handleEditProfile = () => {
  123. window.open('/settings/profile', '_blank');
  124. };
  125. render() {
  126. const { account, intl } = this.props;
  127. let actionBtn;
  128. if (me !== account.get('id')) {
  129. if (!account.get('relationship')) { // Wait until the relationship is loaded
  130. actionBtn = '';
  131. } else if (account.getIn(['relationship', 'requested'])) {
  132. actionBtn = <Button className={classNames('logo-button')} text={intl.formatMessage(messages.cancel_follow_request)} title={intl.formatMessage(messages.requested)} onClick={this.handleFollow} />;
  133. } else if (account.getIn(['relationship', 'muting'])) {
  134. actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unmute)} onClick={this.handleMute} />;
  135. } else if (!account.getIn(['relationship', 'blocking'])) {
  136. actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
  137. } else if (account.getIn(['relationship', 'blocking'])) {
  138. actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock)} onClick={this.handleBlock} />;
  139. }
  140. } else {
  141. actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.edit_profile)} onClick={this.handleEditProfile} />;
  142. }
  143. return (
  144. <div className='account-card'>
  145. <Link to={`/@${account.get('acct')}`} className='account-card__permalink'>
  146. <div className='account-card__header'>
  147. <img
  148. src={
  149. autoPlayGif ? account.get('header') : account.get('header_static')
  150. }
  151. alt=''
  152. />
  153. </div>
  154. <div className='account-card__title'>
  155. <div className='account-card__title__avatar'><Avatar account={account} size={56} /></div>
  156. <DisplayName account={account} />
  157. </div>
  158. </Link>
  159. {account.get('note').length > 0 && (
  160. <div
  161. className='account-card__bio translate'
  162. onMouseEnter={this.handleMouseEnter}
  163. onMouseLeave={this.handleMouseLeave}
  164. dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
  165. />
  166. )}
  167. <div className='account-card__actions'>
  168. <div className='account-card__counters'>
  169. <div className='account-card__counters__item'>
  170. <ShortNumber value={account.get('statuses_count')} />
  171. <small>
  172. <FormattedMessage id='account.posts' defaultMessage='Posts' />
  173. </small>
  174. </div>
  175. <div className='account-card__counters__item'>
  176. <ShortNumber value={account.get('followers_count')} />{' '}
  177. <small>
  178. <FormattedMessage
  179. id='account.followers'
  180. defaultMessage='Followers'
  181. />
  182. </small>
  183. </div>
  184. <div className='account-card__counters__item'>
  185. <ShortNumber value={account.get('following_count')} />{' '}
  186. <small>
  187. <FormattedMessage
  188. id='account.following'
  189. defaultMessage='Following'
  190. />
  191. </small>
  192. </div>
  193. </div>
  194. <div className='account-card__actions__button'>
  195. {actionBtn}
  196. </div>
  197. </div>
  198. </div>
  199. );
  200. }
  201. }
  202. export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(AccountCard));