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.

176 lines
5.3 KiB

  1. import React from 'react';
  2. import { connect } from 'react-redux';
  3. import PropTypes from 'prop-types';
  4. import ImmutablePropTypes from 'react-immutable-proptypes';
  5. import StatusListContainer from '../ui/containers/status_list_container';
  6. import Column from '../../components/column';
  7. import ColumnHeader from '../../components/column_header';
  8. import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
  9. import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
  10. import { connectListStream } from '../../actions/streaming';
  11. import { expandListTimeline } from '../../actions/timelines';
  12. import { fetchList, deleteList } from '../../actions/lists';
  13. import { openModal } from '../../actions/modal';
  14. import MissingIndicator from '../../components/missing_indicator';
  15. import LoadingIndicator from '../../components/loading_indicator';
  16. const messages = defineMessages({
  17. deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
  18. deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' },
  19. });
  20. const mapStateToProps = (state, props) => ({
  21. list: state.getIn(['lists', props.params.id]),
  22. hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
  23. });
  24. @connect(mapStateToProps)
  25. @injectIntl
  26. export default class ListTimeline extends React.PureComponent {
  27. static contextTypes = {
  28. router: PropTypes.object,
  29. };
  30. static propTypes = {
  31. params: PropTypes.object.isRequired,
  32. dispatch: PropTypes.func.isRequired,
  33. shouldUpdateScroll: PropTypes.func,
  34. columnId: PropTypes.string,
  35. hasUnread: PropTypes.bool,
  36. multiColumn: PropTypes.bool,
  37. list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
  38. intl: PropTypes.object.isRequired,
  39. };
  40. handlePin = () => {
  41. const { columnId, dispatch } = this.props;
  42. if (columnId) {
  43. dispatch(removeColumn(columnId));
  44. } else {
  45. dispatch(addColumn('LIST', { id: this.props.params.id }));
  46. this.context.router.history.push('/');
  47. }
  48. }
  49. handleMove = (dir) => {
  50. const { columnId, dispatch } = this.props;
  51. dispatch(moveColumn(columnId, dir));
  52. }
  53. handleHeaderClick = () => {
  54. this.column.scrollTop();
  55. }
  56. componentDidMount () {
  57. const { dispatch } = this.props;
  58. const { id } = this.props.params;
  59. dispatch(fetchList(id));
  60. dispatch(expandListTimeline(id));
  61. this.disconnect = dispatch(connectListStream(id));
  62. }
  63. componentWillUnmount () {
  64. if (this.disconnect) {
  65. this.disconnect();
  66. this.disconnect = null;
  67. }
  68. }
  69. setRef = c => {
  70. this.column = c;
  71. }
  72. handleLoadMore = maxId => {
  73. const { id } = this.props.params;
  74. this.props.dispatch(expandListTimeline(id, { maxId }));
  75. }
  76. handleEditClick = () => {
  77. this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }));
  78. }
  79. handleDeleteClick = () => {
  80. const { dispatch, columnId, intl } = this.props;
  81. const { id } = this.props.params;
  82. dispatch(openModal('CONFIRM', {
  83. message: intl.formatMessage(messages.deleteMessage),
  84. confirm: intl.formatMessage(messages.deleteConfirm),
  85. onConfirm: () => {
  86. dispatch(deleteList(id));
  87. if (!!columnId) {
  88. dispatch(removeColumn(columnId));
  89. } else {
  90. this.context.router.history.push('/lists');
  91. }
  92. },
  93. }));
  94. }
  95. render () {
  96. const { shouldUpdateScroll, hasUnread, columnId, multiColumn, list } = this.props;
  97. const { id } = this.props.params;
  98. const pinned = !!columnId;
  99. const title = list ? list.get('title') : id;
  100. if (typeof list === 'undefined') {
  101. return (
  102. <Column>
  103. <div className='scrollable'>
  104. <LoadingIndicator />
  105. </div>
  106. </Column>
  107. );
  108. } else if (list === false) {
  109. return (
  110. <Column>
  111. <div className='scrollable'>
  112. <MissingIndicator />
  113. </div>
  114. </Column>
  115. );
  116. }
  117. return (
  118. <Column ref={this.setRef}>
  119. <ColumnHeader
  120. icon='list-ul'
  121. active={hasUnread}
  122. title={title}
  123. onPin={this.handlePin}
  124. onMove={this.handleMove}
  125. onClick={this.handleHeaderClick}
  126. pinned={pinned}
  127. multiColumn={multiColumn}
  128. >
  129. <div className='column-header__links'>
  130. <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>
  131. <i className='fa fa-pencil' /> <FormattedMessage id='lists.edit' defaultMessage='Edit list' />
  132. </button>
  133. <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleDeleteClick}>
  134. <i className='fa fa-trash' /> <FormattedMessage id='lists.delete' defaultMessage='Delete list' />
  135. </button>
  136. </div>
  137. <hr />
  138. </ColumnHeader>
  139. <StatusListContainer
  140. trackScroll={!pinned}
  141. scrollKey={`list_timeline-${columnId}`}
  142. timelineId={`list:${id}`}
  143. onLoadMore={this.handleLoadMore}
  144. emptyMessage={<FormattedMessage id='empty_column.list' defaultMessage='There is nothing in this list yet. When members of this list post new statuses, they will appear here.' />}
  145. shouldUpdateScroll={shouldUpdateScroll}
  146. />
  147. </Column>
  148. );
  149. }
  150. }