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.

142 lines
5.2 KiB

  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import PropTypes from 'prop-types';
  5. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  6. import Button from '../../../components/button';
  7. import StatusContent from '../../../components/status_content';
  8. import Avatar from '../../../components/avatar';
  9. import RelativeTimestamp from '../../../components/relative_timestamp';
  10. import DisplayName from '../../../components/display_name';
  11. import ImmutablePureComponent from 'react-immutable-pure-component';
  12. import Icon from 'mastodon/components/icon';
  13. import AttachmentList from 'mastodon/components/attachment_list';
  14. import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown';
  15. import classNames from 'classnames';
  16. import { changeBoostPrivacy } from 'mastodon/actions/boosts';
  17. const messages = defineMessages({
  18. cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
  19. reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
  20. public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
  21. unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
  22. private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
  23. direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
  24. });
  25. const mapStateToProps = state => {
  26. return {
  27. privacy: state.getIn(['boosts', 'new', 'privacy']),
  28. };
  29. };
  30. const mapDispatchToProps = dispatch => {
  31. return {
  32. onChangeBoostPrivacy(value) {
  33. dispatch(changeBoostPrivacy(value));
  34. },
  35. };
  36. };
  37. export default @connect(mapStateToProps, mapDispatchToProps)
  38. @injectIntl
  39. class BoostModal extends ImmutablePureComponent {
  40. static contextTypes = {
  41. router: PropTypes.object,
  42. };
  43. static propTypes = {
  44. status: ImmutablePropTypes.map.isRequired,
  45. onReblog: PropTypes.func.isRequired,
  46. onClose: PropTypes.func.isRequired,
  47. onChangeBoostPrivacy: PropTypes.func.isRequired,
  48. privacy: PropTypes.string.isRequired,
  49. intl: PropTypes.object.isRequired,
  50. };
  51. componentDidMount() {
  52. this.button.focus();
  53. }
  54. handleReblog = () => {
  55. this.props.onReblog(this.props.status, this.props.privacy);
  56. this.props.onClose();
  57. };
  58. handleAccountClick = (e) => {
  59. if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
  60. e.preventDefault();
  61. this.props.onClose();
  62. this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
  63. }
  64. };
  65. _findContainer = () => {
  66. return document.getElementsByClassName('modal-root__container')[0];
  67. };
  68. setRef = (c) => {
  69. this.button = c;
  70. };
  71. render () {
  72. const { status, privacy, intl } = this.props;
  73. const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog;
  74. const visibilityIconInfo = {
  75. 'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
  76. 'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
  77. 'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
  78. 'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
  79. };
  80. const visibilityIcon = visibilityIconInfo[status.get('visibility')];
  81. return (
  82. <div className='modal-root__modal boost-modal'>
  83. <div className='boost-modal__container'>
  84. <div className={classNames('status', `status-${status.get('visibility')}`, 'light')}>
  85. <div className='status__info'>
  86. <a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
  87. <span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
  88. <RelativeTimestamp timestamp={status.get('created_at')} />
  89. </a>
  90. <a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name'>
  91. <div className='status__avatar'>
  92. <Avatar account={status.get('account')} size={48} />
  93. </div>
  94. <DisplayName account={status.get('account')} />
  95. </a>
  96. </div>
  97. <StatusContent status={status} />
  98. {status.get('media_attachments').size > 0 && (
  99. <AttachmentList
  100. compact
  101. media={status.get('media_attachments')}
  102. />
  103. )}
  104. </div>
  105. </div>
  106. <div className='boost-modal__action-bar'>
  107. <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} /></div>
  108. {status.get('visibility') !== 'private' && !status.get('reblogged') && (
  109. <PrivacyDropdown
  110. noDirect
  111. value={privacy}
  112. container={this._findContainer}
  113. onChange={this.props.onChangeBoostPrivacy}
  114. />
  115. )}
  116. <Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
  117. </div>
  118. </div>
  119. );
  120. }
  121. }