b0664a5e6c
onScrollToBottom was a function to run instead of onScrollToTop and onScroll when scrolling to the bottom. The behavior to prevent onScrollToTop was inconvenient because the viewport can be at the bottom and at the top at the same time if the viewport is larger than the container. onScrollToBottom was also called when the button to load more is clicked on contray to the name suggests, which led notifications and status_list_container components to mark the scrolled location is not at the top mistakenly. onLoadMore is a replacement for onScrollToBottom. It will be called independently from onScrollToTop and onScroll.
175 lines
5.3 KiB
JavaScript
175 lines
5.3 KiB
JavaScript
import React from 'react';
|
|
import { connect } from 'react-redux';
|
|
import PropTypes from 'prop-types';
|
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
import Column from '../../components/column';
|
|
import ColumnHeader from '../../components/column_header';
|
|
import { expandNotifications, scrollTopNotifications } from '../../actions/notifications';
|
|
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
|
import NotificationContainer from './containers/notification_container';
|
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
import ColumnSettingsContainer from './containers/column_settings_container';
|
|
import { createSelector } from 'reselect';
|
|
import { List as ImmutableList } from 'immutable';
|
|
import { debounce } from 'lodash';
|
|
import ScrollableList from '../../components/scrollable_list';
|
|
|
|
const messages = defineMessages({
|
|
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
|
});
|
|
|
|
const getNotifications = createSelector([
|
|
state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
|
|
state => state.getIn(['notifications', 'items']),
|
|
], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));
|
|
|
|
const mapStateToProps = state => ({
|
|
notifications: getNotifications(state),
|
|
isLoading: state.getIn(['notifications', 'isLoading'], true),
|
|
isUnread: state.getIn(['notifications', 'unread']) > 0,
|
|
hasMore: !!state.getIn(['notifications', 'next']),
|
|
});
|
|
|
|
@connect(mapStateToProps)
|
|
@injectIntl
|
|
export default class Notifications extends React.PureComponent {
|
|
|
|
static propTypes = {
|
|
columnId: PropTypes.string,
|
|
notifications: ImmutablePropTypes.list.isRequired,
|
|
dispatch: PropTypes.func.isRequired,
|
|
shouldUpdateScroll: PropTypes.func,
|
|
intl: PropTypes.object.isRequired,
|
|
isLoading: PropTypes.bool,
|
|
isUnread: PropTypes.bool,
|
|
multiColumn: PropTypes.bool,
|
|
hasMore: PropTypes.bool,
|
|
};
|
|
|
|
static defaultProps = {
|
|
trackScroll: true,
|
|
};
|
|
|
|
componentWillUnmount () {
|
|
this.handleLoadMore.cancel();
|
|
this.handleScrollToTop.cancel();
|
|
this.handleScroll.cancel();
|
|
this.props.dispatch(scrollTopNotifications(false));
|
|
}
|
|
|
|
handleLoadMore = debounce(() => {
|
|
this.props.dispatch(expandNotifications());
|
|
}, 300, { leading: true });
|
|
|
|
handleScrollToTop = debounce(() => {
|
|
this.props.dispatch(scrollTopNotifications(true));
|
|
}, 100);
|
|
|
|
handleScroll = debounce(() => {
|
|
this.props.dispatch(scrollTopNotifications(false));
|
|
}, 100);
|
|
|
|
handlePin = () => {
|
|
const { columnId, dispatch } = this.props;
|
|
|
|
if (columnId) {
|
|
dispatch(removeColumn(columnId));
|
|
} else {
|
|
dispatch(addColumn('NOTIFICATIONS', {}));
|
|
}
|
|
}
|
|
|
|
handleMove = (dir) => {
|
|
const { columnId, dispatch } = this.props;
|
|
dispatch(moveColumn(columnId, dir));
|
|
}
|
|
|
|
handleHeaderClick = () => {
|
|
this.column.scrollTop();
|
|
}
|
|
|
|
setColumnRef = c => {
|
|
this.column = c;
|
|
}
|
|
|
|
handleMoveUp = id => {
|
|
const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) - 1;
|
|
this._selectChild(elementIndex);
|
|
}
|
|
|
|
handleMoveDown = id => {
|
|
const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) + 1;
|
|
this._selectChild(elementIndex);
|
|
}
|
|
|
|
_selectChild (index) {
|
|
const element = this.column.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
|
|
|
if (element) {
|
|
element.focus();
|
|
}
|
|
}
|
|
|
|
render () {
|
|
const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props;
|
|
const pinned = !!columnId;
|
|
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;
|
|
|
|
let scrollableContent = null;
|
|
|
|
if (isLoading && this.scrollableContent) {
|
|
scrollableContent = this.scrollableContent;
|
|
} else if (notifications.size > 0 || hasMore) {
|
|
scrollableContent = notifications.map((item) => (
|
|
<NotificationContainer
|
|
key={item.get('id')}
|
|
notification={item}
|
|
accountId={item.get('account')}
|
|
onMoveUp={this.handleMoveUp}
|
|
onMoveDown={this.handleMoveDown}
|
|
/>
|
|
));
|
|
} else {
|
|
scrollableContent = null;
|
|
}
|
|
|
|
this.scrollableContent = scrollableContent;
|
|
|
|
const scrollContainer = (
|
|
<ScrollableList
|
|
scrollKey={`notifications-${columnId}`}
|
|
trackScroll={!pinned}
|
|
isLoading={isLoading}
|
|
hasMore={hasMore}
|
|
emptyMessage={emptyMessage}
|
|
onLoadMore={this.handleLoadMore}
|
|
onScrollToTop={this.handleScrollToTop}
|
|
onScroll={this.handleScroll}
|
|
shouldUpdateScroll={shouldUpdateScroll}
|
|
>
|
|
{scrollableContent}
|
|
</ScrollableList>
|
|
);
|
|
|
|
return (
|
|
<Column ref={this.setColumnRef}>
|
|
<ColumnHeader
|
|
icon='bell'
|
|
active={isUnread}
|
|
title={intl.formatMessage(messages.title)}
|
|
onPin={this.handlePin}
|
|
onMove={this.handleMove}
|
|
onClick={this.handleHeaderClick}
|
|
pinned={pinned}
|
|
multiColumn={multiColumn}
|
|
>
|
|
<ColumnSettingsContainer />
|
|
</ColumnHeader>
|
|
|
|
{scrollContainer}
|
|
</Column>
|
|
);
|
|
}
|
|
|
|
}
|