Files
talk/client/coral-embed-stream/src/components/AllCommentsPane.js
T

227 lines
6.6 KiB
JavaScript

import React from 'react';
import PropTypes from 'prop-types';
import LoadMore from './LoadMore';
import NewCount from './NewCount';
import {TransitionGroup} from 'react-transition-group';
import {forEachError} from 'coral-framework/utils';
import Comment from '../containers/Comment';
import NoComments from './NoComments';
const hasComment = (nodes, id) => nodes.some((node) => node.id === id);
// resetCursors will return the id cursors of the first and second comment of
// the current comment list. The cursors are used to dertermine which
// comments to show. The spare cursor functions as a backup in case one
// of the comments gets deleted.
function resetCursors(state, props) {
const comments = props.root.asset.comments;
if (comments && comments.nodes.length) {
const idCursors = [comments.nodes[0].id];
if (comments.nodes[1]) {
idCursors.push(comments.nodes[1].id);
}
return {idCursors};
}
return {idCursors: []};
}
// invalidateCursor is called whenever a comment is removed which is referenced
// by one of the 2 id cursors. It returns a new set of id cursors calculated
// using the help of the backup cursor.
function invalidateCursor(invalidated, state, props) {
const alt = invalidated === 1 ? 0 : 1;
const comments = props.root.asset.comments;
const idCursors = [];
if (state.idCursors[alt]) {
idCursors.push(state.idCursors[alt]);
const index = comments.nodes.findIndex((node) => node.id === idCursors[0]);
const nextInLine = comments.nodes[index + 1];
if (nextInLine) {
idCursors.push(nextInLine.id);
}
}
return {idCursors};
}
class AllCommentsPane extends React.Component {
constructor(props) {
super(props);
this.state = {
...resetCursors(this.state, props),
loadingState: '',
};
}
componentWillReceiveProps(next) {
const {comments: prevComments} = this.props;
const {comments: nextComments} = next;
if (!prevComments && nextComments) {
this.setState(resetCursors);
return;
}
if (
prevComments && nextComments &&
nextComments.nodes.length < prevComments.nodes.length
) {
// Invalidate first cursor if referenced comment was removed.
if (this.state.idCursors[0] && !hasComment(nextComments.nodes, this.state.idCursors[0])) {
this.setState(invalidateCursor(0, this.state, next));
}
// Invalidate second cursor if referenced comment was removed.
if (this.state.idCursors[1] && !hasComment(nextComments.nodes, this.state.idCursors[1])) {
this.setState(invalidateCursor(1, this.state, next));
}
}
}
loadMore = () => {
this.setState({loadingState: 'loading'});
this.props.loadMore()
.then(() => {
this.setState({loadingState: 'success'});
})
.catch((error) => {
this.setState({loadingState: 'error'});
forEachError(error, ({msg}) => {this.props.notify('error', msg);});
});
}
viewNewComments = () => {
this.setState(resetCursors);
this.props.emit('ui.AllCommentsPane.viewNewComments');
};
// getVisibileComments returns a list containing comments
// which were authored by current user or comes after the `idCursor`.
getVisibleComments() {
const {comments, currentUser: user} = this.props;
const idCursor = this.state.idCursors[0];
const userId = user ? user.id : null;
if (!comments) {
return [];
}
const view = [];
let pastCursor = false;
comments.nodes.forEach((comment) => {
if (comment.id === idCursor) {
pastCursor = true;
}
if (pastCursor || comment.user.id === userId) {
view.push(comment);
}
});
return view;
}
render() {
const {
data,
root,
comments,
commentClassNames,
setActiveReplyBox,
activeReplyBox,
notify,
disableReply,
postComment,
asset,
currentUser,
postFlag,
postDontAgree,
loadNewReplies,
deleteAction,
showSignInDialog,
charCountEnable,
maxCharCount,
editComment,
emit,
} = this.props;
const {loadingState} = this.state;
const visibleComments = this.getVisibleComments();
return (
<div>
{visibleComments.length ? (
<div className="talk-stream-comments-container">
<NewCount
count={comments.nodes.length - visibleComments.length}
loadMore={this.viewNewComments}
/>
<TransitionGroup component='div' className="embed__stream">
{visibleComments.map((comment) => {
return (
<Comment
commentClassNames={commentClassNames}
data={data}
root={root}
disableReply={disableReply}
setActiveReplyBox={setActiveReplyBox}
activeReplyBox={activeReplyBox}
notify={notify}
depth={0}
postComment={postComment}
asset={asset}
currentUser={currentUser}
postFlag={postFlag}
postDontAgree={postDontAgree}
loadMore={loadNewReplies}
deleteAction={deleteAction}
showSignInDialog={showSignInDialog}
key={comment.id}
comment={comment}
charCountEnable={charCountEnable}
maxCharCount={maxCharCount}
editComment={editComment}
emit={emit}
/>
);
})}
</TransitionGroup>
<LoadMore
topLevel={true}
moreComments={asset.comments.hasNextPage}
loadMore={this.loadMore}
loadingState={loadingState}
/>
</div>
) : (
<NoComments assetClosed={asset.isClosed} />
)}
</div>
);
}
}
AllCommentsPane.propTypes = {
data: PropTypes.object,
root: PropTypes.object,
comments: PropTypes.object,
commentClassNames: PropTypes.array,
setActiveReplyBox: PropTypes.func,
activeReplyBox: PropTypes.string,
notify: PropTypes.func,
disableReply: PropTypes.bool,
postComment: PropTypes.func,
asset: PropTypes.object,
currentUser: PropTypes.object,
postFlag: PropTypes.func,
postDontAgree: PropTypes.func,
loadNewReplies: PropTypes.func,
deleteAction: PropTypes.func,
showSignInDialog: PropTypes.func,
charCountEnable: PropTypes.bool,
maxCharCount: PropTypes.number,
editComment: PropTypes.func,
emit: PropTypes.func,
};
export default AllCommentsPane;