index.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  4. import PropTypes from 'prop-types';
  5. import StatusListContainer from '../ui/containers/status_list_container';
  6. import Column from '../../components/column';
  7. import ColumnHeader from '../../components/column_header';
  8. import { expandPublicTimeline } from '../../actions/timelines';
  9. import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
  10. import ColumnSettingsContainer from './containers/column_settings_container';
  11. import { connectPublicStream } from '../../actions/streaming';
  12. import { Helmet } from 'react-helmet';
  13. import DismissableBanner from 'mastodon/components/dismissable_banner';
  14. const messages = defineMessages({
  15. title: { id: 'column.public', defaultMessage: 'Federated timeline' },
  16. });
  17. const mapStateToProps = (state, { columnId }) => {
  18. const uuid = columnId;
  19. const columns = state.getIn(['settings', 'columns']);
  20. const index = columns.findIndex(c => c.get('uuid') === uuid);
  21. const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
  22. const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
  23. const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
  24. return {
  25. hasUnread: !!timelineState && timelineState.get('unread') > 0,
  26. onlyMedia,
  27. onlyRemote,
  28. };
  29. };
  30. export default @connect(mapStateToProps)
  31. @injectIntl
  32. class PublicTimeline extends React.PureComponent {
  33. static contextTypes = {
  34. router: PropTypes.object,
  35. identity: PropTypes.object,
  36. };
  37. static defaultProps = {
  38. onlyMedia: false,
  39. };
  40. static propTypes = {
  41. dispatch: PropTypes.func.isRequired,
  42. intl: PropTypes.object.isRequired,
  43. columnId: PropTypes.string,
  44. multiColumn: PropTypes.bool,
  45. hasUnread: PropTypes.bool,
  46. onlyMedia: PropTypes.bool,
  47. onlyRemote: PropTypes.bool,
  48. };
  49. handlePin = () => {
  50. const { columnId, dispatch, onlyMedia, onlyRemote } = this.props;
  51. if (columnId) {
  52. dispatch(removeColumn(columnId));
  53. } else {
  54. dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } }));
  55. }
  56. }
  57. handleMove = (dir) => {
  58. const { columnId, dispatch } = this.props;
  59. dispatch(moveColumn(columnId, dir));
  60. }
  61. handleHeaderClick = () => {
  62. this.column.scrollTop();
  63. }
  64. componentDidMount () {
  65. const { dispatch, onlyMedia, onlyRemote } = this.props;
  66. const { signedIn } = this.context.identity;
  67. dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
  68. if (signedIn) {
  69. this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
  70. }
  71. }
  72. componentDidUpdate (prevProps) {
  73. const { signedIn } = this.context.identity;
  74. if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
  75. const { dispatch, onlyMedia, onlyRemote } = this.props;
  76. if (this.disconnect) {
  77. this.disconnect();
  78. }
  79. dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
  80. if (signedIn) {
  81. this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
  82. }
  83. }
  84. }
  85. componentWillUnmount () {
  86. if (this.disconnect) {
  87. this.disconnect();
  88. this.disconnect = null;
  89. }
  90. }
  91. setRef = c => {
  92. this.column = c;
  93. }
  94. handleLoadMore = maxId => {
  95. const { dispatch, onlyMedia, onlyRemote } = this.props;
  96. dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote }));
  97. }
  98. render () {
  99. const { intl, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
  100. const pinned = !!columnId;
  101. return (
  102. <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
  103. <ColumnHeader
  104. icon='globe'
  105. active={hasUnread}
  106. title={intl.formatMessage(messages.title)}
  107. onPin={this.handlePin}
  108. onMove={this.handleMove}
  109. onClick={this.handleHeaderClick}
  110. pinned={pinned}
  111. multiColumn={multiColumn}
  112. >
  113. <ColumnSettingsContainer columnId={columnId} />
  114. </ColumnHeader>
  115. <DismissableBanner id='public_timeline'>
  116. <FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' />
  117. </DismissableBanner>
  118. <StatusListContainer
  119. timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
  120. onLoadMore={this.handleLoadMore}
  121. trackScroll={!pinned}
  122. scrollKey={`public_timeline-${columnId}`}
  123. emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
  124. bindToDocument={!multiColumn}
  125. />
  126. <Helmet>
  127. <title>{intl.formatMessage(messages.title)}</title>
  128. <meta name='robots' content='noindex' />
  129. </Helmet>
  130. </Column>
  131. );
  132. }
  133. }