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.

164 lines
5.1 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}${props.params.local ? ':local' : ''}`, '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. 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 key='any' 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 key='all' 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 key='none' 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 = {}, local) {
  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, local, 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, local } = this.props.params;
  81. this._subscribe(dispatch, id, tags, local);
  82. dispatch(expandHashtagTimeline(id, { tags, local }));
  83. }
  84. componentWillReceiveProps (nextProps) {
  85. const { dispatch, params } = this.props;
  86. const { id, tags, local } = nextProps.params;
  87. if (id !== params.id || !isEqual(tags, params.tags) || !isEqual(local, params.local)) {
  88. this._unsubscribe();
  89. this._subscribe(dispatch, id, tags, local);
  90. dispatch(clearTimeline(`hashtag:${id}${local ? ':local' : ''}`));
  91. dispatch(expandHashtagTimeline(id, { tags, local }));
  92. }
  93. }
  94. componentWillUnmount () {
  95. this._unsubscribe();
  96. }
  97. setRef = c => {
  98. this.column = c;
  99. }
  100. handleLoadMore = maxId => {
  101. const { id, tags, local } = this.props.params;
  102. this.props.dispatch(expandHashtagTimeline(id, { maxId, tags, local }));
  103. }
  104. render () {
  105. const { hasUnread, columnId, multiColumn } = this.props;
  106. const { id, local } = this.props.params;
  107. const pinned = !!columnId;
  108. return (
  109. <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
  110. <ColumnHeader
  111. icon='hashtag'
  112. active={hasUnread}
  113. title={this.title()}
  114. onPin={this.handlePin}
  115. onMove={this.handleMove}
  116. onClick={this.handleHeaderClick}
  117. pinned={pinned}
  118. multiColumn={multiColumn}
  119. showBackButton
  120. >
  121. {columnId && <ColumnSettingsContainer columnId={columnId} />}
  122. </ColumnHeader>
  123. <StatusListContainer
  124. trackScroll={!pinned}
  125. scrollKey={`hashtag_timeline-${columnId}`}
  126. timelineId={`hashtag:${id}${local ? ':local' : ''}`}
  127. onLoadMore={this.handleLoadMore}
  128. emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
  129. bindToDocument={!multiColumn}
  130. />
  131. </Column>
  132. );
  133. }
  134. }