闭社主体 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.

142 lines
4.8 KiB

  1. import { connect } from 'react-redux';
  2. import PureRenderMixin from 'react-addons-pure-render-mixin';
  3. import ImmutablePropTypes from 'react-immutable-proptypes';
  4. import Column from '../ui/components/column';
  5. import { expandNotifications, clearNotifications, scrollTopNotifications } from '../../actions/notifications';
  6. import NotificationContainer from './containers/notification_container';
  7. import { ScrollContainer } from 'react-router-scroll';
  8. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  9. import ColumnSettingsContainer from './containers/column_settings_container';
  10. import { createSelector } from 'reselect';
  11. import Immutable from 'immutable';
  12. import LoadMore from '../../components/load_more';
  13. import ClearColumnButton from './components/clear_column_button';
  14. const messages = defineMessages({
  15. title: { id: 'column.notifications', defaultMessage: 'Notifications' },
  16. confirm: { id: 'notifications.clear_confirmation', defaultMessage: 'Are you sure you want to clear all your notifications?' }
  17. });
  18. const getNotifications = createSelector([
  19. state => Immutable.List(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
  20. state => state.getIn(['notifications', 'items'])
  21. ], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));
  22. const mapStateToProps = state => ({
  23. notifications: getNotifications(state),
  24. isLoading: state.getIn(['notifications', 'isLoading'], true),
  25. isUnread: state.getIn(['notifications', 'unread']) > 0
  26. });
  27. const Notifications = React.createClass({
  28. propTypes: {
  29. notifications: ImmutablePropTypes.list.isRequired,
  30. dispatch: React.PropTypes.func.isRequired,
  31. trackScroll: React.PropTypes.bool,
  32. intl: React.PropTypes.object.isRequired,
  33. isLoading: React.PropTypes.bool,
  34. isUnread: React.PropTypes.bool
  35. },
  36. getDefaultProps () {
  37. return {
  38. trackScroll: true
  39. };
  40. },
  41. mixins: [PureRenderMixin],
  42. handleScroll (e) {
  43. const { scrollTop, scrollHeight, clientHeight } = e.target;
  44. const offset = scrollHeight - scrollTop - clientHeight;
  45. this._oldScrollPosition = scrollHeight - scrollTop;
  46. if (250 > offset && !this.props.isLoading) {
  47. this.props.dispatch(expandNotifications());
  48. } else if (scrollTop < 100) {
  49. this.props.dispatch(scrollTopNotifications(true));
  50. } else {
  51. this.props.dispatch(scrollTopNotifications(false));
  52. }
  53. },
  54. componentDidUpdate (prevProps) {
  55. if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) {
  56. this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition;
  57. }
  58. },
  59. handleLoadMore (e) {
  60. e.preventDefault();
  61. this.props.dispatch(expandNotifications());
  62. },
  63. handleClear () {
  64. if (window.confirm(this.props.intl.formatMessage(messages.confirm))) {
  65. this.props.dispatch(clearNotifications());
  66. }
  67. },
  68. setRef (c) {
  69. this.node = c;
  70. },
  71. render () {
  72. const { intl, notifications, trackScroll, isLoading, isUnread } = this.props;
  73. let loadMore = '';
  74. let scrollableArea = '';
  75. let unread = '';
  76. if (!isLoading && notifications.size > 0) {
  77. loadMore = <LoadMore onClick={this.handleLoadMore} />;
  78. }
  79. if (isUnread) {
  80. unread = <div className='notifications__unread-indicator' />;
  81. }
  82. if (isLoading || notifications.size > 0) {
  83. scrollableArea = (
  84. <div className='scrollable' onScroll={this.handleScroll} ref={this.setRef}>
  85. {unread}
  86. <div>
  87. {notifications.map(item => <NotificationContainer key={item.get('id')} notification={item} accountId={item.get('account')} />)}
  88. {loadMore}
  89. </div>
  90. </div>
  91. );
  92. } else {
  93. scrollableArea = (
  94. <div className='empty-column-indicator' ref={this.setRef}>
  95. <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />
  96. </div>
  97. );
  98. }
  99. if (trackScroll) {
  100. return (
  101. <Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
  102. <ColumnSettingsContainer />
  103. <ClearColumnButton onClick={this.handleClear} />
  104. <ScrollContainer scrollKey='notifications'>
  105. {scrollableArea}
  106. </ScrollContainer>
  107. </Column>
  108. );
  109. } else {
  110. return (
  111. <Column icon='bell' active={isUnread} heading={intl.formatMessage(messages.title)}>
  112. <ColumnSettingsContainer />
  113. <ClearColumnButton onClick={this.handleClear} />
  114. {scrollableArea}
  115. </Column>
  116. );
  117. }
  118. }
  119. });
  120. export default connect(mapStateToProps)(injectIntl(Notifications));