闭社主体 forked from https://github.com/tootsuite/mastodon
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.

146 lines
4.1 KiB

  1. import React from 'react';
  2. import ImmutablePropTypes from 'react-immutable-proptypes';
  3. import { ScrollContainer } from 'react-router-scroll';
  4. import PropTypes from 'prop-types';
  5. import StatusContainer from '../containers/status_container';
  6. import LoadMore from './load_more';
  7. import ImmutablePureComponent from 'react-immutable-pure-component';
  8. import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
  9. import { debounce } from 'lodash';
  10. class StatusList extends ImmutablePureComponent {
  11. static propTypes = {
  12. scrollKey: PropTypes.string.isRequired,
  13. statusIds: ImmutablePropTypes.list.isRequired,
  14. onScrollToBottom: PropTypes.func,
  15. onScrollToTop: PropTypes.func,
  16. onScroll: PropTypes.func,
  17. trackScroll: PropTypes.bool,
  18. shouldUpdateScroll: PropTypes.func,
  19. isLoading: PropTypes.bool,
  20. hasMore: PropTypes.bool,
  21. prepend: PropTypes.node,
  22. emptyMessage: PropTypes.node,
  23. };
  24. static defaultProps = {
  25. trackScroll: true,
  26. };
  27. intersectionObserverWrapper = new IntersectionObserverWrapper();
  28. handleScroll = debounce((e) => {
  29. const { scrollTop, scrollHeight, clientHeight } = e.target;
  30. const offset = scrollHeight - scrollTop - clientHeight;
  31. this._oldScrollPosition = scrollHeight - scrollTop;
  32. if (250 > offset && this.props.onScrollToBottom && !this.props.isLoading) {
  33. this.props.onScrollToBottom();
  34. } else if (scrollTop < 100 && this.props.onScrollToTop) {
  35. this.props.onScrollToTop();
  36. } else if (this.props.onScroll) {
  37. this.props.onScroll();
  38. }
  39. }, 200, {
  40. trailing: true,
  41. });
  42. componentDidMount () {
  43. this.attachScrollListener();
  44. this.attachIntersectionObserver();
  45. }
  46. componentDidUpdate (prevProps) {
  47. // Reset the scroll position when a new toot comes in in order not to
  48. // jerk the scrollbar around if you're already scrolled down the page.
  49. if (prevProps.statusIds.size < this.props.statusIds.size &&
  50. prevProps.statusIds.first() !== this.props.statusIds.first() &&
  51. this._oldScrollPosition &&
  52. this.node.scrollTop > 0) {
  53. let newScrollTop = this.node.scrollHeight - this._oldScrollPosition;
  54. if (this.node.scrollTop !== newScrollTop) {
  55. this.node.scrollTop = newScrollTop;
  56. }
  57. }
  58. }
  59. componentWillUnmount () {
  60. this.detachScrollListener();
  61. this.detachIntersectionObserver();
  62. }
  63. attachIntersectionObserver () {
  64. this.intersectionObserverWrapper.connect({
  65. root: this.node,
  66. rootMargin: '300% 0px',
  67. });
  68. }
  69. detachIntersectionObserver () {
  70. this.intersectionObserverWrapper.disconnect();
  71. }
  72. attachScrollListener () {
  73. this.node.addEventListener('scroll', this.handleScroll);
  74. }
  75. detachScrollListener () {
  76. this.node.removeEventListener('scroll', this.handleScroll);
  77. }
  78. setRef = (c) => {
  79. this.node = c;
  80. }
  81. handleLoadMore = (e) => {
  82. e.preventDefault();
  83. this.props.onScrollToBottom();
  84. }
  85. render () {
  86. const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
  87. let loadMore = null;
  88. let scrollableArea = null;
  89. if (!isLoading && statusIds.size > 0 && hasMore) {
  90. loadMore = <LoadMore onClick={this.handleLoadMore} />;
  91. }
  92. if (isLoading || statusIds.size > 0 || !emptyMessage) {
  93. scrollableArea = (
  94. <div className='scrollable' ref={this.setRef}>
  95. <div className='status-list'>
  96. {prepend}
  97. {statusIds.map((statusId) => {
  98. return <StatusContainer key={statusId} id={statusId} intersectionObserverWrapper={this.intersectionObserverWrapper} />;
  99. })}
  100. {loadMore}
  101. </div>
  102. </div>
  103. );
  104. } else {
  105. scrollableArea = (
  106. <div className='empty-column-indicator' ref={this.setRef}>
  107. {emptyMessage}
  108. </div>
  109. );
  110. }
  111. if (trackScroll) {
  112. return (
  113. <ScrollContainer scrollKey={scrollKey} shouldUpdateScroll={shouldUpdateScroll}>
  114. {scrollableArea}
  115. </ScrollContainer>
  116. );
  117. } else {
  118. return scrollableArea;
  119. }
  120. }
  121. }
  122. export default StatusList;