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.

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