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.

157 lines
4.9 KiB

7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import StatusListContainer from '../ui/containers/status_list_container';
  5. import Column from '../../components/column';
  6. import ColumnHeader from '../../components/column_header';
  7. import ColumnSettingsContainer from './containers/column_settings_container';
  8. import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
  9. import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
  10. import { FormattedMessage } from 'react-intl';
  11. import { connectHashtagStream } from '../../actions/streaming';
  12. const mapStateToProps = (state, props) => ({
  13. hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
  14. });
  15. export default @connect(mapStateToProps)
  16. class HashtagTimeline extends React.PureComponent {
  17. disconnects = [];
  18. static propTypes = {
  19. params: PropTypes.object.isRequired,
  20. columnId: PropTypes.string,
  21. dispatch: PropTypes.func.isRequired,
  22. shouldUpdateScroll: PropTypes.func,
  23. hasUnread: PropTypes.bool,
  24. multiColumn: PropTypes.bool,
  25. };
  26. handlePin = () => {
  27. const { columnId, dispatch } = this.props;
  28. if (columnId) {
  29. dispatch(removeColumn(columnId));
  30. } else {
  31. dispatch(addColumn('HASHTAG', { id: this.props.params.id }));
  32. }
  33. }
  34. title = () => {
  35. let title = [this.props.params.id];
  36. if (this.additionalFor('any')) {
  37. title.push(<FormattedMessage id='hashtag.column_header.tag_mode.any' values={{ additional: this.additionalFor('any') }} defaultMessage=' or {additional}' />);
  38. }
  39. if (this.additionalFor('all')) {
  40. title.push(<FormattedMessage id='hashtag.column_header.tag_mode.all' values={{ additional: this.additionalFor('all') }} defaultMessage=' and {additional}' />);
  41. }
  42. if (this.additionalFor('none')) {
  43. title.push(<FormattedMessage id='hashtag.column_header.tag_mode.none' values={{ additional: this.additionalFor('none') }} defaultMessage=' without {additional}' />);
  44. }
  45. return title;
  46. }
  47. additionalFor = (mode) => {
  48. const { tags } = this.props.params;
  49. if (tags && (tags[mode] || []).length > 0) {
  50. return tags[mode].map(tag => tag.value).join('/');
  51. } else {
  52. return '';
  53. }
  54. }
  55. handleMove = (dir) => {
  56. const { columnId, dispatch } = this.props;
  57. dispatch(moveColumn(columnId, dir));
  58. }
  59. handleHeaderClick = () => {
  60. this.column.scrollTop();
  61. }
  62. _subscribe (dispatch, id, tags = {}) {
  63. let any = (tags.any || []).map(tag => tag.value);
  64. let all = (tags.all || []).map(tag => tag.value);
  65. let none = (tags.none || []).map(tag => tag.value);
  66. [id, ...any].map((tag) => {
  67. this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
  68. let tags = status.tags.map(tag => tag.name);
  69. return all.filter(tag => tags.includes(tag)).length === all.length &&
  70. none.filter(tag => tags.includes(tag)).length === 0;
  71. })));
  72. });
  73. }
  74. _unsubscribe () {
  75. this.disconnects.map(disconnect => disconnect());
  76. this.disconnects = [];
  77. }
  78. componentDidMount () {
  79. const { dispatch } = this.props;
  80. const { id, tags } = this.props.params;
  81. dispatch(expandHashtagTimeline(id, { tags }));
  82. }
  83. componentWillReceiveProps (nextProps) {
  84. const { dispatch, params } = this.props;
  85. const { id, tags } = nextProps.params;
  86. if (id !== params.id || tags !== params.tags) {
  87. this._unsubscribe();
  88. this._subscribe(dispatch, id, tags);
  89. this.props.dispatch(clearTimeline(`hashtag:${id}`));
  90. this.props.dispatch(expandHashtagTimeline(id, { tags }));
  91. }
  92. }
  93. componentWillUnmount () {
  94. this._unsubscribe();
  95. }
  96. setRef = c => {
  97. this.column = c;
  98. }
  99. handleLoadMore = maxId => {
  100. const { id, tags } = this.props.params;
  101. this.props.dispatch(expandHashtagTimeline(id, { maxId, tags }));
  102. }
  103. render () {
  104. const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
  105. const { id } = this.props.params;
  106. const pinned = !!columnId;
  107. return (
  108. <Column ref={this.setRef} label={`#${id}`}>
  109. <ColumnHeader
  110. icon='hashtag'
  111. active={hasUnread}
  112. title={this.title()}
  113. onPin={this.handlePin}
  114. onMove={this.handleMove}
  115. onClick={this.handleHeaderClick}
  116. pinned={pinned}
  117. multiColumn={multiColumn}
  118. showBackButton
  119. >
  120. {columnId && <ColumnSettingsContainer columnId={columnId} />}
  121. </ColumnHeader>
  122. <StatusListContainer
  123. trackScroll={!pinned}
  124. scrollKey={`hashtag_timeline-${columnId}`}
  125. timelineId={`hashtag:${id}`}
  126. onLoadMore={this.handleLoadMore}
  127. emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
  128. shouldUpdateScroll={shouldUpdateScroll}
  129. />
  130. </Column>
  131. );
  132. }
  133. }