Files
talk/client/coral-embed-stream/src/containers/Stream.js
T

332 lines
8.9 KiB
JavaScript

import React from 'react';
import {gql, compose} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {ADDTL_COMMENTS_ON_LOAD_MORE, THREADING_LEVEL} from '../constants/stream';
import {
withPostComment, withPostFlag, withPostDontAgree,
withDeleteAction, withIgnoreUser, withEditComment
} from 'coral-framework/graphql/mutations';
import {notificationActions, authActions} from 'coral-framework';
import {editName} from 'coral-framework/actions/user';
import {setActiveReplyBox} from '../actions/stream';
import Stream from '../components/Stream';
import Comment from './Comment';
import {withFragments} from 'coral-framework/hocs';
import {Spinner} from 'coral-ui';
import {getDefinitionName} from 'coral-framework/utils';
import {
findCommentInEmbedQuery,
insertCommentIntoEmbedQuery,
removeCommentFromEmbedQuery,
insertFetchedCommentsIntoEmbedQuery,
} from '../graphql/utils';
const {showSignInDialog} = authActions;
const {addNotification} = notificationActions;
class StreamContainer extends React.Component {
subscriptions = [];
subscribeToUpdates() {
const newSubscriptions = [{
document: COMMENTS_EDITED_SUBSCRIPTION,
updateQuery: (prev, {subscriptionData: {data: {commentEdited}}}) => {
// Ignore mutations from me.
// TODO: need way to detect mutations created by this client, and allow mutations from other clients.
if (this.props.auth.user && commentEdited.user.id === this.props.auth.user.id) {
return prev;
}
// Exit when comment is not in the query.
if (!findCommentInEmbedQuery(prev, commentEdited.id)) {
return prev;
}
if (['PREMOD', 'REJECTED'].includes(commentEdited.status)) {
return removeCommentFromEmbedQuery(prev, commentEdited.id);
}
},
},
{
document: COMMENTS_ADDED_SUBSCRIPTION,
updateQuery: (prev, {subscriptionData: {data: {commentAdded}}}) => {
// Ignore mutations from me.
// TODO: need way to detect mutations created by this client, and allow mutations from other clients.
if (this.props.auth.user && commentAdded.user.id === this.props.auth.user.id) {
return prev;
}
// Exit if author is ignored.
if (
this.props.root.me &&
this.props.root.me.ignoredUsers.some(({id}) => id === commentAdded.user.id)) {
return prev;
}
// Exit when comment is already in the query.
if (findCommentInEmbedQuery(prev, commentAdded.id)) {
return prev;
}
return insertCommentIntoEmbedQuery(prev, commentAdded);
}
}];
this.subscriptions = newSubscriptions.map((s) => this.props.data.subscribeToMore({
document: s.document,
variables: {
assetId: this.props.root.asset.id,
},
updateQuery: s.updateQuery,
}));
}
unsubscribe() {
this.subscriptions.forEach((unsubscribe) => unsubscribe());
this.subscriptions = [];
}
loadNewReplies = (parent_id) => {
const comment = findCommentInEmbedQuery(this.props.root, parent_id);
return this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
variables: {
limit: parent_id ? 999999 : ADDTL_COMMENTS_ON_LOAD_MORE,
cursor: comment.replies.endCursor,
parent_id,
asset_id: this.props.root.asset.id,
sort: 'CHRONOLOGICAL',
excludeIgnored: this.props.data.variables.excludeIgnored,
},
updateQuery: (prev, {fetchMoreResult:{comments}}) => {
return insertFetchedCommentsIntoEmbedQuery(prev, comments, parent_id);
},
});
}
loadMoreComments = () => {
return this.props.data.fetchMore({
query: LOAD_MORE_QUERY,
variables: {
limit: ADDTL_COMMENTS_ON_LOAD_MORE,
cursor: this.props.root.asset.comments.endCursor,
parent_id: null,
asset_id: this.props.root.asset.id,
sort: 'REVERSE_CHRONOLOGICAL',
excludeIgnored: this.props.data.variables.excludeIgnored,
},
updateQuery: (prev, {fetchMoreResult:{comments}}) => {
return insertFetchedCommentsIntoEmbedQuery(prev, comments);
},
});
};
componentDidMount() {
if (this.props.previousTab) {
this.props.data.refetch();
}
this.subscribeToUpdates();
}
componentWillUnmount() {
this.unsubscribe();
clearInterval(this.countPoll);
}
render() {
if (this.props.refetching) {
return <Spinner />;
}
return <Stream
{...this.props}
loadMore={this.loadMore}
loadMoreComments={this.loadMoreComments}
loadNewReplies={this.loadNewReplies}
/>;
}
}
const nest = (def, level) => {
let result = '';
for (let x = 0; x < level; x++) {
if (x === 0) {
result += def;
continue;
}
result = result.replace('...nest', def);
}
return result.replace('...nest', '');
};
const commentFragment = gql`
fragment CoralEmbedStream_Stream_comment on Comment {
id
...${getDefinitionName(Comment.fragments.comment)}
${nest(`
replyCount(excludeIgnored: $excludeIgnored)
replies(excludeIgnored: $excludeIgnored) {
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
...nest
}
hasNextPage
startCursor
endCursor
}
`, THREADING_LEVEL)}
}
${Comment.fragments.comment}
`;
const COMMENTS_ADDED_SUBSCRIPTION = gql`
subscription CommentAdded($assetId: ID!, $excludeIgnored: Boolean){
commentAdded(asset_id: $assetId){
parent {
id
}
...CoralEmbedStream_Stream_comment
}
}
${commentFragment}
`;
const COMMENTS_EDITED_SUBSCRIPTION = gql`
subscription CommentEdited($assetId: ID!){
commentEdited(asset_id: $assetId){
id
body
status
editing {
edited
}
user {
id
}
}
}
`;
const LOAD_MORE_QUERY = gql`
query CoralEmbedStream_LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) {
comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) {
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
${nest(`
replyCount(excludeIgnored: $excludeIgnored)
replies(limit: 3, excludeIgnored: $excludeIgnored) {
nodes {
id
...${getDefinitionName(Comment.fragments.comment)}
...nest
}
hasNextPage
startCursor
endCursor
}
`, THREADING_LEVEL)}
}
hasNextPage
startCursor
endCursor
}
}
${Comment.fragments.comment}
`;
const fragments = {
root: gql`
fragment CoralEmbedStream_Stream_root on RootQuery {
comment(id: $commentId) @include(if: $hasComment) {
...CoralEmbedStream_Stream_comment
${nest(`
parent {
...CoralEmbedStream_Stream_comment
...nest
}
`, THREADING_LEVEL)}
}
asset(id: $assetId, url: $assetUrl) {
id
title
url
closedAt
created_at
settings {
moderation
infoBoxEnable
infoBoxContent
premodLinksEnable
questionBoxEnable
questionBoxContent
closeTimeout
closedMessage
charCountEnable
charCount
requireEmailConfirmation
}
commentCount(excludeIgnored: $excludeIgnored)
totalCommentCount(excludeIgnored: $excludeIgnored)
comments(limit: 10, excludeIgnored: $excludeIgnored) @skip(if: $hasComment) {
nodes {
...CoralEmbedStream_Stream_comment
}
hasNextPage
startCursor
endCursor
}
}
me {
status
ignoredUsers {
id
}
}
settings {
organizationName
}
...${getDefinitionName(Comment.fragments.root)}
}
${Comment.fragments.root}
${commentFragment}
`,
};
const mapStateToProps = (state) => ({
auth: state.auth.toJS(),
refetching: state.embed.refetching,
commentCountCache: state.stream.commentCountCache,
activeReplyBox: state.stream.activeReplyBox,
commentId: state.stream.commentId,
assetId: state.stream.assetId,
assetUrl: state.stream.assetUrl,
activeTab: state.embed.activeTab,
previousTab: state.embed.previousTab,
commentClassNames: state.stream.commentClassNames
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
showSignInDialog,
addNotification,
setActiveReplyBox,
editName,
}, dispatch);
export default compose(
withFragments(fragments),
connect(mapStateToProps, mapDispatchToProps),
withPostComment,
withPostFlag,
withPostDontAgree,
withIgnoreUser,
withDeleteAction,
withEditComment,
)(StreamContainer);