footer.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import ImmutablePureComponent from 'react-immutable-pure-component';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import PropTypes from 'prop-types';
  6. import IconButton from 'mastodon/components/icon_button';
  7. import classNames from 'classnames';
  8. import { me, boostModal } from 'mastodon/initial_state';
  9. import { defineMessages, injectIntl } from 'react-intl';
  10. import { replyCompose } from 'mastodon/actions/compose';
  11. import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
  12. import { makeGetStatus } from 'mastodon/selectors';
  13. import { initBoostModal } from 'mastodon/actions/boosts';
  14. import { openModal } from 'mastodon/actions/modal';
  15. const messages = defineMessages({
  16. reply: { id: 'status.reply', defaultMessage: 'Reply' },
  17. replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
  18. reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
  19. reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
  20. cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
  21. cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
  22. favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
  23. replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
  24. replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
  25. open: { id: 'status.open', defaultMessage: 'Expand this status' },
  26. });
  27. const makeMapStateToProps = () => {
  28. const getStatus = makeGetStatus();
  29. const mapStateToProps = (state, { statusId }) => ({
  30. status: getStatus(state, { id: statusId }),
  31. askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
  32. });
  33. return mapStateToProps;
  34. };
  35. export default @connect(makeMapStateToProps)
  36. @injectIntl
  37. class Footer extends ImmutablePureComponent {
  38. static contextTypes = {
  39. router: PropTypes.object,
  40. identity: PropTypes.object,
  41. };
  42. static propTypes = {
  43. statusId: PropTypes.string.isRequired,
  44. status: ImmutablePropTypes.map.isRequired,
  45. intl: PropTypes.object.isRequired,
  46. dispatch: PropTypes.func.isRequired,
  47. askReplyConfirmation: PropTypes.bool,
  48. withOpenButton: PropTypes.bool,
  49. onClose: PropTypes.func,
  50. };
  51. _performReply = () => {
  52. const { dispatch, status, onClose } = this.props;
  53. const { router } = this.context;
  54. if (onClose) {
  55. onClose(true);
  56. }
  57. dispatch(replyCompose(status, router.history));
  58. };
  59. handleReplyClick = () => {
  60. const { dispatch, askReplyConfirmation, status, intl } = this.props;
  61. const { signedIn } = this.context.identity;
  62. if (signedIn) {
  63. if (askReplyConfirmation) {
  64. dispatch(openModal('CONFIRM', {
  65. message: intl.formatMessage(messages.replyMessage),
  66. confirm: intl.formatMessage(messages.replyConfirm),
  67. onConfirm: this._performReply,
  68. }));
  69. } else {
  70. this._performReply();
  71. }
  72. } else {
  73. dispatch(openModal('INTERACTION', {
  74. type: 'reply',
  75. accountId: status.getIn(['account', 'id']),
  76. url: status.get('url'),
  77. }));
  78. }
  79. };
  80. handleFavouriteClick = () => {
  81. const { dispatch, status } = this.props;
  82. const { signedIn } = this.context.identity;
  83. if (signedIn) {
  84. if (status.get('favourited')) {
  85. dispatch(unfavourite(status));
  86. } else {
  87. dispatch(favourite(status));
  88. }
  89. } else {
  90. dispatch(openModal('INTERACTION', {
  91. type: 'favourite',
  92. accountId: status.getIn(['account', 'id']),
  93. url: status.get('url'),
  94. }));
  95. }
  96. };
  97. _performReblog = (status, privacy) => {
  98. const { dispatch } = this.props;
  99. dispatch(reblog(status, privacy));
  100. }
  101. handleReblogClick = e => {
  102. const { dispatch, status } = this.props;
  103. const { signedIn } = this.context.identity;
  104. if (signedIn) {
  105. if (status.get('reblogged')) {
  106. dispatch(unreblog(status));
  107. } else if ((e && e.shiftKey) || !boostModal) {
  108. this._performReblog(status);
  109. } else {
  110. dispatch(initBoostModal({ status, onReblog: this._performReblog }));
  111. }
  112. } else {
  113. dispatch(openModal('INTERACTION', {
  114. type: 'reblog',
  115. accountId: status.getIn(['account', 'id']),
  116. url: status.get('url'),
  117. }));
  118. }
  119. };
  120. handleOpenClick = e => {
  121. const { router } = this.context;
  122. if (e.button !== 0 || !router) {
  123. return;
  124. }
  125. const { status, onClose } = this.props;
  126. if (onClose) {
  127. onClose();
  128. }
  129. router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
  130. }
  131. render () {
  132. const { status, intl, withOpenButton } = this.props;
  133. const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
  134. const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
  135. let replyIcon, replyTitle;
  136. if (status.get('in_reply_to_id', null) === null) {
  137. replyIcon = 'reply';
  138. replyTitle = intl.formatMessage(messages.reply);
  139. } else {
  140. replyIcon = 'reply-all';
  141. replyTitle = intl.formatMessage(messages.replyAll);
  142. }
  143. let reblogTitle = '';
  144. if (status.get('reblogged')) {
  145. reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
  146. } else if (publicStatus) {
  147. reblogTitle = intl.formatMessage(messages.reblog);
  148. } else if (reblogPrivate) {
  149. reblogTitle = intl.formatMessage(messages.reblog_private);
  150. } else {
  151. reblogTitle = intl.formatMessage(messages.cannot_reblog);
  152. }
  153. return (
  154. <div className='picture-in-picture__footer'>
  155. <IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
  156. <IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
  157. <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
  158. {withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} href={status.get('url')} />}
  159. </div>
  160. );
  161. }
  162. }