The code powering m.abunchtell.com https://m.abunchtell.com
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

221 righe
7.6 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 Column from '../../components/column';
  6. import ColumnHeader from '../../components/column_header';
  7. import { expandNotifications, scrollTopNotifications, loadPending } from '../../actions/notifications';
  8. import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
  9. import NotificationContainer from './containers/notification_container';
  10. import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  11. import ColumnSettingsContainer from './containers/column_settings_container';
  12. import FilterBarContainer from './containers/filter_bar_container';
  13. import { createSelector } from 'reselect';
  14. import { List as ImmutableList } from 'immutable';
  15. import { debounce } from 'lodash';
  16. import ScrollableList from '../../components/scrollable_list';
  17. import LoadGap from '../../components/load_gap';
  18. const messages = defineMessages({
  19. title: { id: 'column.notifications', defaultMessage: 'Notifications' },
  20. });
  21. const getNotifications = createSelector([
  22. state => state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
  23. state => state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
  24. state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
  25. state => state.getIn(['notifications', 'items']),
  26. ], (showFilterBar, allowedType, excludedTypes, notifications) => {
  27. if (!showFilterBar || allowedType === 'all') {
  28. // used if user changed the notification settings after loading the notifications from the server
  29. // otherwise a list of notifications will come pre-filtered from the backend
  30. // we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
  31. return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
  32. }
  33. return notifications.filter(item => item !== null && allowedType === item.get('type'));
  34. });
  35. const mapStateToProps = state => ({
  36. showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
  37. notifications: getNotifications(state),
  38. isLoading: state.getIn(['notifications', 'isLoading'], true),
  39. isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
  40. hasMore: state.getIn(['notifications', 'hasMore']),
  41. numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
  42. });
  43. export default @connect(mapStateToProps)
  44. @injectIntl
  45. class Notifications extends React.PureComponent {
  46. static propTypes = {
  47. columnId: PropTypes.string,
  48. notifications: ImmutablePropTypes.list.isRequired,
  49. showFilterBar: PropTypes.bool.isRequired,
  50. dispatch: PropTypes.func.isRequired,
  51. shouldUpdateScroll: PropTypes.func,
  52. intl: PropTypes.object.isRequired,
  53. isLoading: PropTypes.bool,
  54. isUnread: PropTypes.bool,
  55. multiColumn: PropTypes.bool,
  56. hasMore: PropTypes.bool,
  57. numPending: PropTypes.number,
  58. };
  59. static defaultProps = {
  60. trackScroll: true,
  61. };
  62. componentWillUnmount () {
  63. this.handleLoadOlder.cancel();
  64. this.handleScrollToTop.cancel();
  65. this.handleScroll.cancel();
  66. this.props.dispatch(scrollTopNotifications(false));
  67. }
  68. handleLoadGap = (maxId) => {
  69. this.props.dispatch(expandNotifications({ maxId }));
  70. };
  71. handleLoadOlder = debounce(() => {
  72. const last = this.props.notifications.last();
  73. this.props.dispatch(expandNotifications({ maxId: last && last.get('id') }));
  74. }, 300, { leading: true });
  75. handleLoadPending = () => {
  76. this.props.dispatch(loadPending());
  77. };
  78. handleScrollToTop = debounce(() => {
  79. this.props.dispatch(scrollTopNotifications(true));
  80. }, 100);
  81. handleScroll = debounce(() => {
  82. this.props.dispatch(scrollTopNotifications(false));
  83. }, 100);
  84. handlePin = () => {
  85. const { columnId, dispatch } = this.props;
  86. if (columnId) {
  87. dispatch(removeColumn(columnId));
  88. } else {
  89. dispatch(addColumn('NOTIFICATIONS', {}));
  90. }
  91. }
  92. handleMove = (dir) => {
  93. const { columnId, dispatch } = this.props;
  94. dispatch(moveColumn(columnId, dir));
  95. }
  96. handleHeaderClick = () => {
  97. this.column.scrollTop();
  98. }
  99. setColumnRef = c => {
  100. this.column = c;
  101. }
  102. handleMoveUp = id => {
  103. const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
  104. this._selectChild(elementIndex, true);
  105. }
  106. handleMoveDown = id => {
  107. const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
  108. this._selectChild(elementIndex, false);
  109. }
  110. _selectChild (index, align_top) {
  111. const container = this.column.node;
  112. const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
  113. if (element) {
  114. if (align_top && container.scrollTop > element.offsetTop) {
  115. element.scrollIntoView(true);
  116. } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
  117. element.scrollIntoView(false);
  118. }
  119. element.focus();
  120. }
  121. }
  122. render () {
  123. const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar } = this.props;
  124. const pinned = !!columnId;
  125. const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
  126. let scrollableContent = null;
  127. const filterBarContainer = showFilterBar
  128. ? (<FilterBarContainer />)
  129. : null;
  130. if (isLoading && this.scrollableContent) {
  131. scrollableContent = this.scrollableContent;
  132. } else if (notifications.size > 0 || hasMore) {
  133. scrollableContent = notifications.map((item, index) => item === null ? (
  134. <LoadGap
  135. key={'gap:' + notifications.getIn([index + 1, 'id'])}
  136. disabled={isLoading}
  137. maxId={index > 0 ? notifications.getIn([index - 1, 'id']) : null}
  138. onClick={this.handleLoadGap}
  139. />
  140. ) : (
  141. <NotificationContainer
  142. key={item.get('id')}
  143. notification={item}
  144. accountId={item.get('account')}
  145. onMoveUp={this.handleMoveUp}
  146. onMoveDown={this.handleMoveDown}
  147. />
  148. ));
  149. } else {
  150. scrollableContent = null;
  151. }
  152. this.scrollableContent = scrollableContent;
  153. const scrollContainer = (
  154. <ScrollableList
  155. scrollKey={`notifications-${columnId}`}
  156. trackScroll={!pinned}
  157. isLoading={isLoading}
  158. showLoading={isLoading && notifications.size === 0}
  159. hasMore={hasMore}
  160. numPending={numPending}
  161. emptyMessage={emptyMessage}
  162. onLoadMore={this.handleLoadOlder}
  163. onLoadPending={this.handleLoadPending}
  164. onScrollToTop={this.handleScrollToTop}
  165. onScroll={this.handleScroll}
  166. shouldUpdateScroll={shouldUpdateScroll}
  167. bindToDocument={!multiColumn}
  168. >
  169. {scrollableContent}
  170. </ScrollableList>
  171. );
  172. return (
  173. <Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}>
  174. <ColumnHeader
  175. icon='bell'
  176. active={isUnread}
  177. title={intl.formatMessage(messages.title)}
  178. onPin={this.handlePin}
  179. onMove={this.handleMove}
  180. onClick={this.handleHeaderClick}
  181. pinned={pinned}
  182. multiColumn={multiColumn}
  183. >
  184. <ColumnSettingsContainer />
  185. </ColumnHeader>
  186. {filterBarContainer}
  187. {scrollContainer}
  188. </Column>
  189. );
  190. }
  191. }