status_list.jsx 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import PropTypes from 'prop-types';
  2. import ImmutablePropTypes from 'react-immutable-proptypes';
  3. import ImmutablePureComponent from 'react-immutable-pure-component';
  4. import { debounce } from 'lodash';
  5. import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
  6. import StatusContainer from '../containers/status_container';
  7. import { LoadGap } from './load_gap';
  8. import ScrollableList from './scrollable_list';
  9. export default class StatusList extends ImmutablePureComponent {
  10. static propTypes = {
  11. scrollKey: PropTypes.string.isRequired,
  12. statusIds: ImmutablePropTypes.list.isRequired,
  13. featuredStatusIds: ImmutablePropTypes.list,
  14. onLoadMore: PropTypes.func,
  15. onScrollToTop: PropTypes.func,
  16. onScroll: PropTypes.func,
  17. trackScroll: PropTypes.bool,
  18. isLoading: PropTypes.bool,
  19. isPartial: PropTypes.bool,
  20. hasMore: PropTypes.bool,
  21. prepend: PropTypes.node,
  22. emptyMessage: PropTypes.node,
  23. alwaysPrepend: PropTypes.bool,
  24. withCounters: PropTypes.bool,
  25. timelineId: PropTypes.string,
  26. lastId: PropTypes.string,
  27. };
  28. static defaultProps = {
  29. trackScroll: true,
  30. };
  31. getFeaturedStatusCount = () => {
  32. return this.props.featuredStatusIds ? this.props.featuredStatusIds.size : 0;
  33. };
  34. getCurrentStatusIndex = (id, featured) => {
  35. if (featured) {
  36. return this.props.featuredStatusIds.indexOf(id);
  37. } else {
  38. return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount();
  39. }
  40. };
  41. handleMoveUp = (id, featured) => {
  42. const elementIndex = this.getCurrentStatusIndex(id, featured) - 1;
  43. this._selectChild(elementIndex, true);
  44. };
  45. handleMoveDown = (id, featured) => {
  46. const elementIndex = this.getCurrentStatusIndex(id, featured) + 1;
  47. this._selectChild(elementIndex, false);
  48. };
  49. handleLoadOlder = debounce(() => {
  50. const { statusIds, lastId, onLoadMore } = this.props;
  51. onLoadMore(lastId || (statusIds.size > 0 ? statusIds.last() : undefined));
  52. }, 300, { leading: true });
  53. _selectChild (index, align_top) {
  54. const container = this.node.node;
  55. const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
  56. if (element) {
  57. if (align_top && container.scrollTop > element.offsetTop) {
  58. element.scrollIntoView(true);
  59. } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
  60. element.scrollIntoView(false);
  61. }
  62. element.focus();
  63. }
  64. }
  65. setRef = c => {
  66. this.node = c;
  67. };
  68. render () {
  69. const { statusIds, featuredStatusIds, onLoadMore, timelineId, ...other } = this.props;
  70. const { isLoading, isPartial } = other;
  71. if (isPartial) {
  72. return <RegenerationIndicator />;
  73. }
  74. let scrollableContent = (isLoading || statusIds.size > 0) ? (
  75. statusIds.map((statusId, index) => statusId === null ? (
  76. <LoadGap
  77. key={'gap:' + statusIds.get(index + 1)}
  78. disabled={isLoading}
  79. maxId={index > 0 ? statusIds.get(index - 1) : null}
  80. onClick={onLoadMore}
  81. />
  82. ) : (
  83. <StatusContainer
  84. key={statusId}
  85. id={statusId}
  86. onMoveUp={this.handleMoveUp}
  87. onMoveDown={this.handleMoveDown}
  88. contextType={timelineId}
  89. scrollKey={this.props.scrollKey}
  90. showThread
  91. withCounters={this.props.withCounters}
  92. />
  93. ))
  94. ) : null;
  95. if (scrollableContent && featuredStatusIds) {
  96. scrollableContent = featuredStatusIds.map(statusId => (
  97. <StatusContainer
  98. key={`f-${statusId}`}
  99. id={statusId}
  100. featured
  101. onMoveUp={this.handleMoveUp}
  102. onMoveDown={this.handleMoveDown}
  103. contextType={timelineId}
  104. showThread
  105. withCounters={this.props.withCounters}
  106. />
  107. )).concat(scrollableContent);
  108. }
  109. return (
  110. <ScrollableList {...other} showLoading={isLoading && statusIds.size === 0} onLoadMore={onLoadMore && this.handleLoadOlder} ref={this.setRef}>
  111. {scrollableContent}
  112. </ScrollableList>
  113. );
  114. }
  115. }