mirror of
https://github.com/wassname/talk.git
synced 2026-08-01 13:00:55 +08:00
Refactor pagination
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {add} from 'coral-framework/services/graphqlRegistry';
|
||||
import update from 'immutability-helper';
|
||||
const queues = ['all', 'premod', 'flagged', 'accepted', 'rejected'];
|
||||
|
||||
const extension = {
|
||||
@@ -11,54 +12,53 @@ const extension = {
|
||||
}),
|
||||
SetCommentStatus: ({variables: {commentId, status}}) => ({
|
||||
updateQueries: {
|
||||
CoralAdmin_Moderation: (oldData) => {
|
||||
CoralAdmin_Moderation: (prev) => {
|
||||
const comment = queues.reduce((comment, queue) => {
|
||||
return comment ? comment : oldData[queue].find((c) => c.id === commentId);
|
||||
return comment ? comment : prev[queue].nodes.find((c) => c.id === commentId);
|
||||
}, null);
|
||||
|
||||
let accepted = oldData.accepted;
|
||||
let acceptedCount = oldData.acceptedCount;
|
||||
let rejected = oldData.rejected;
|
||||
let rejectedCount = oldData.rejectedCount;
|
||||
let acceptedNodes = prev.accepted.nodes;
|
||||
let acceptedCount = prev.acceptedCount;
|
||||
let rejectedNodes = prev.rejected.nodes;
|
||||
let rejectedCount = prev.rejectedCount;
|
||||
|
||||
if (status !== comment.status) {
|
||||
if (status === 'ACCEPTED') {
|
||||
comment.status = 'ACCEPTED';
|
||||
acceptedCount++;
|
||||
accepted = [comment, ...accepted];
|
||||
acceptedNodes = [comment, ...acceptedNodes];
|
||||
}
|
||||
else if (status === 'REJECTED') {
|
||||
comment.status = 'REJECTED';
|
||||
rejectedCount++;
|
||||
rejected = [comment, ...rejected];
|
||||
rejectedNodes = [comment, ...rejectedNodes];
|
||||
}
|
||||
}
|
||||
|
||||
const premod = oldData.premod.filter((c) => c.id !== commentId);
|
||||
const flagged = oldData.flagged.filter((c) => c.id !== commentId);
|
||||
const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount;
|
||||
const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount;
|
||||
const premodNodes = prev.premod.nodes.filter((c) => c.id !== commentId);
|
||||
const flaggedNodes = prev.flagged.nodes.filter((c) => c.id !== commentId);
|
||||
const premodCount = premodNodes.length < prev.premod.nodes.length ? prev.premodCount - 1 : prev.premodCount;
|
||||
const flaggedCount = flaggedNodes.length < prev.flagged.nodes.length ? prev.flaggedCount - 1 : prev.flaggedCount;
|
||||
|
||||
if (status === 'REJECTED') {
|
||||
accepted = oldData.accepted.filter((c) => c.id !== commentId);
|
||||
acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount;
|
||||
acceptedNodes = prev.accepted.nodes.filter((c) => c.id !== commentId);
|
||||
acceptedCount = acceptedNodes.length < prev.accepted.nodes.length ? prev.acceptedCount - 1 : prev.acceptedCount;
|
||||
}
|
||||
else if (status === 'ACCEPTED') {
|
||||
rejected = oldData.rejected.filter((c) => c.id !== commentId);
|
||||
rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount;
|
||||
rejectedNodes = prev.rejected.nodes.filter((c) => c.id !== commentId);
|
||||
rejectedCount = rejectedNodes.length < prev.rejected.nodes.length ? prev.rejectedCount - 1 : prev.rejectedCount;
|
||||
}
|
||||
|
||||
return {
|
||||
...oldData,
|
||||
premodCount: Math.max(0, premodCount),
|
||||
flaggedCount: Math.max(0, flaggedCount),
|
||||
acceptedCount: Math.max(0, acceptedCount),
|
||||
rejectedCount: Math.max(0, rejectedCount),
|
||||
premod,
|
||||
flagged,
|
||||
accepted,
|
||||
rejected,
|
||||
};
|
||||
return update(prev, {
|
||||
premodCount: {$set: Math.max(0, premodCount)},
|
||||
flaggedCount: {$set: Math.max(0, flaggedCount)},
|
||||
acceptedCount: {$set: Math.max(0, acceptedCount)},
|
||||
rejectedCount: {$set: Math.max(0, rejectedCount)},
|
||||
premod: {nodes: {$set: premodNodes}},
|
||||
flagged: {nodes: {$set: flaggedNodes}},
|
||||
accepted: {nodes: {$set: acceptedNodes}},
|
||||
rejected: {nodes: {$set: rejectedNodes}},
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
@@ -2,32 +2,19 @@ import React, {PropTypes} from 'react';
|
||||
import {Button} from 'coral-ui';
|
||||
import styles from './styles.css';
|
||||
|
||||
const LoadMore = ({comments, loadMore, sort, tab, assetId, showLoadMore}) =>
|
||||
const LoadMore = ({loadMore, showLoadMore}) =>
|
||||
<div className={styles.loadMoreContainer}>
|
||||
{
|
||||
showLoadMore && <Button
|
||||
className={styles.loadMore}
|
||||
onClick={() => {
|
||||
const lastComment = comments[comments.length - 1];
|
||||
const cursor = lastComment ? lastComment.created_at : null;
|
||||
return loadMore({
|
||||
cursor,
|
||||
sort,
|
||||
tab,
|
||||
asset_id: assetId
|
||||
});
|
||||
}}>
|
||||
onClick={loadMore}>
|
||||
Load More
|
||||
</Button>
|
||||
}
|
||||
</div>;
|
||||
|
||||
LoadMore.propTypes = {
|
||||
comments: PropTypes.array.isRequired,
|
||||
loadMore: PropTypes.func.isRequired,
|
||||
sort: PropTypes.oneOf(['CHRONOLOGICAL', 'REVERSE_CHRONOLOGICAL']).isRequired,
|
||||
tab: PropTypes.oneOf(['rejected', 'premod', 'flagged', 'all', 'accepted']).isRequired,
|
||||
assetId: PropTypes.string,
|
||||
showLoadMore: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ export default class Moderation extends Component {
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
currentAsset={asset}
|
||||
comments={comments}
|
||||
comments={comments.nodes}
|
||||
activeTab={activeTab}
|
||||
singleView={moderation.singleView}
|
||||
selectedIndex={this.state.selectedIndex}
|
||||
|
||||
@@ -21,14 +21,18 @@ class ModerationQueue extends React.Component {
|
||||
comments: PropTypes.array.isRequired
|
||||
}
|
||||
|
||||
loadMore = () => {
|
||||
this.props.loadMore(this.props.activeTab);
|
||||
}
|
||||
|
||||
componentDidUpdate (prev) {
|
||||
const {loadMore, comments, commentCount, sort, activeTab: tab, assetId: asset_id} = this.props;
|
||||
const {comments, commentCount} = this.props;
|
||||
|
||||
// if the user just moderated the last (visible) comment
|
||||
// AND there are more comments available on the server,
|
||||
// go ahead and load more comments
|
||||
if (prev.comments.length > 0 && comments.length === 0 && commentCount > 0) {
|
||||
loadMore({sort, tab, asset_id});
|
||||
this.loadMore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +42,6 @@ class ModerationQueue extends React.Component {
|
||||
selectedIndex,
|
||||
commentCount,
|
||||
singleView,
|
||||
loadMore,
|
||||
activeTab,
|
||||
sort,
|
||||
viewUserDetail,
|
||||
...props
|
||||
} = this.props;
|
||||
@@ -75,12 +76,8 @@ class ModerationQueue extends React.Component {
|
||||
}
|
||||
</ul>
|
||||
<LoadMore
|
||||
comments={comments}
|
||||
loadMore={loadMore}
|
||||
sort={sort}
|
||||
tab={activeTab}
|
||||
loadMore={this.loadMore}
|
||||
showLoadMore={comments.length < commentCount}
|
||||
assetId={props.assetId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import * as notification from 'coral-admin/src/services/notification';
|
||||
import t, {timeago} from 'coral-framework/services/i18n';
|
||||
import update from 'immutability-helper';
|
||||
|
||||
import {withSetUserStatus, withSuspendUser, withSetCommentStatus} from 'coral-framework/graphql/mutations';
|
||||
|
||||
@@ -80,12 +81,12 @@ class ModerationContainer extends Component {
|
||||
return this.props.setCommentStatus({commentId, status: 'REJECTED'});
|
||||
}
|
||||
|
||||
loadMore = ({limit = 10, cursor, sort, tab, asset_id}) => {
|
||||
let variables = {
|
||||
limit,
|
||||
cursor,
|
||||
sort,
|
||||
asset_id
|
||||
loadMore = (tab) => {
|
||||
const variables = {
|
||||
limit: 10,
|
||||
cursor: this.props.root[tab].endCursor,
|
||||
sort: this.props.data.variables.sort,
|
||||
asset_id: this.props.data.variables.asset_id,
|
||||
};
|
||||
switch(tab) {
|
||||
case 'all':
|
||||
@@ -108,14 +109,12 @@ class ModerationContainer extends Component {
|
||||
return this.props.data.fetchMore({
|
||||
query: LOAD_MORE_QUERY,
|
||||
variables,
|
||||
updateQuery: (oldData, {fetchMoreResult:{comments}}) => {
|
||||
return {
|
||||
...oldData,
|
||||
[tab]: [
|
||||
...oldData[tab],
|
||||
...comments
|
||||
]
|
||||
};
|
||||
updateQuery: (prev, {fetchMoreResult:{comments}}) => {
|
||||
return update(prev, {
|
||||
[tab]: {
|
||||
nodes: {$push: comments.nodes},
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -145,11 +144,13 @@ class ModerationContainer extends Component {
|
||||
const LOAD_MORE_QUERY = gql`
|
||||
query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) {
|
||||
comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
action_summaries {
|
||||
count
|
||||
... on FlagActionSummary {
|
||||
reason
|
||||
nodes {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
action_summaries {
|
||||
count
|
||||
... on FlagActionSummary {
|
||||
reason
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,6 +158,18 @@ const LOAD_MORE_QUERY = gql`
|
||||
${Comment.fragments.comment}
|
||||
`;
|
||||
|
||||
const commentConnectionFragment = gql`
|
||||
fragment CoralAdmin_Moderation_CommentConnection on CommentConnection {
|
||||
nodes {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
}
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
${Comment.fragments.comment}
|
||||
`;
|
||||
|
||||
const withModQueueQuery = withQuery(gql`
|
||||
query CoralAdmin_Moderation($asset_id: ID, $sort: SORT_ORDER) {
|
||||
all: comments(query: {
|
||||
@@ -164,21 +177,21 @@ const withModQueueQuery = withQuery(gql`
|
||||
asset_id: $asset_id,
|
||||
sort: $sort
|
||||
}) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
...CoralAdmin_Moderation_CommentConnection
|
||||
}
|
||||
accepted: comments(query: {
|
||||
statuses: [ACCEPTED],
|
||||
asset_id: $asset_id,
|
||||
sort: $sort
|
||||
}) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
...CoralAdmin_Moderation_CommentConnection
|
||||
}
|
||||
premod: comments(query: {
|
||||
statuses: [PREMOD],
|
||||
asset_id: $asset_id,
|
||||
sort: $sort
|
||||
}) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
...CoralAdmin_Moderation_CommentConnection
|
||||
}
|
||||
flagged: comments(query: {
|
||||
action_type: FLAG,
|
||||
@@ -186,14 +199,14 @@ const withModQueueQuery = withQuery(gql`
|
||||
statuses: [NONE, PREMOD],
|
||||
sort: $sort
|
||||
}) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
...CoralAdmin_Moderation_CommentConnection
|
||||
}
|
||||
rejected: comments(query: {
|
||||
statuses: [REJECTED],
|
||||
asset_id: $asset_id,
|
||||
sort: $sort
|
||||
}) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
...CoralAdmin_Moderation_CommentConnection
|
||||
}
|
||||
assets: assets {
|
||||
id
|
||||
@@ -224,7 +237,7 @@ const withModQueueQuery = withQuery(gql`
|
||||
organizationName
|
||||
}
|
||||
}
|
||||
${Comment.fragments.comment}
|
||||
${commentConnectionFragment}
|
||||
`, {
|
||||
options: ({params: {id = null}, moderation: {sortOrder}}) => {
|
||||
return {
|
||||
|
||||
@@ -87,12 +87,7 @@ class Comment extends React.Component {
|
||||
name: PropTypes.string
|
||||
})
|
||||
),
|
||||
replies: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
body: PropTypes.string.isRequired,
|
||||
id: PropTypes.string.isRequired
|
||||
})
|
||||
),
|
||||
replies: PropTypes.object,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired
|
||||
@@ -195,7 +190,7 @@ class Comment extends React.Component {
|
||||
let commentClass = parentId
|
||||
? `reply ${styles.Reply}`
|
||||
: `comment ${styles.Comment}`;
|
||||
commentClass += comment.id === 'pending' ? ` ${styles.pendingComment}` : '';
|
||||
commentClass += comment.id.indexOf('pending') >= 0 ? ` ${styles.pendingComment}` : '';
|
||||
|
||||
// call a function, and if it errors, call addNotification('error', ...) (e.g. to show user a snackbar)
|
||||
const notifyOnError = (fn, errorToMessage) =>
|
||||
@@ -375,7 +370,7 @@ class Comment extends React.Component {
|
||||
/>
|
||||
: null}
|
||||
{comment.replies &&
|
||||
comment.replies.map((reply) => {
|
||||
comment.replies.nodes.map((reply) => {
|
||||
return commentIsIgnored(reply)
|
||||
? <IgnoredCommentTombstone key={reply.id} />
|
||||
: <Comment
|
||||
@@ -408,13 +403,10 @@ class Comment extends React.Component {
|
||||
{comment.replies &&
|
||||
<div className="coral-load-more-replies">
|
||||
<LoadMore
|
||||
assetId={asset.id}
|
||||
comments={comment.replies}
|
||||
parentId={comment.id}
|
||||
topLevel={false}
|
||||
replyCount={comment.replyCount}
|
||||
moreComments={comment.replyCount > comment.replies.length}
|
||||
loadMore={loadMore}
|
||||
moreComments={comment.replyCount > comment.replies.nodes.length}
|
||||
loadMore={() => loadMore(comment.id)}
|
||||
/>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
@@ -1,26 +1,7 @@
|
||||
import React, {PropTypes} from 'react';
|
||||
import {ADDTL_COMMENTS_ON_LOAD_MORE} from '../constants/stream';
|
||||
import {Button} from 'coral-ui';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const loadMoreComments = (assetId, comments, loadMore, parentId, replyCount) => {
|
||||
|
||||
let cursor = null;
|
||||
if (comments.length) {
|
||||
cursor = parentId
|
||||
? comments[0].created_at
|
||||
: comments[comments.length - 1].created_at;
|
||||
}
|
||||
|
||||
loadMore({
|
||||
limit: parentId ? replyCount : ADDTL_COMMENTS_ON_LOAD_MORE,
|
||||
cursor,
|
||||
asset_id: assetId,
|
||||
parent_id: parentId,
|
||||
sort: parentId ? 'CHRONOLOGICAL' : 'REVERSE_CHRONOLOGICAL'
|
||||
});
|
||||
};
|
||||
|
||||
class LoadMore extends React.Component {
|
||||
|
||||
componentDidMount () {
|
||||
@@ -40,13 +21,13 @@ class LoadMore extends React.Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
const {assetId, comments, loadMore, moreComments, parentId, replyCount, topLevel} = this.props;
|
||||
const {topLevel, moreComments, loadMore, replyCount} = this.props;
|
||||
return moreComments
|
||||
? <div className='coral-load-more'>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.initialState = false;
|
||||
loadMoreComments(assetId, comments, loadMore, parentId, replyCount);
|
||||
loadMore();
|
||||
}}>
|
||||
{topLevel ? t('framework.view_more_comments') : this.replyCountFormat(replyCount)}
|
||||
</Button>
|
||||
@@ -56,11 +37,8 @@ class LoadMore extends React.Component {
|
||||
}
|
||||
|
||||
LoadMore.propTypes = {
|
||||
assetId: PropTypes.string.isRequired,
|
||||
comments: PropTypes.array.isRequired,
|
||||
moreComments: PropTypes.bool.isRequired,
|
||||
topLevel: PropTypes.bool.isRequired,
|
||||
replyCount: PropTypes.number,
|
||||
topLevel: PropTypes.bool.isRequired,
|
||||
loadMore: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ class Stream extends React.Component {
|
||||
addNotification,
|
||||
postFlag,
|
||||
postDontAgree,
|
||||
loadMore,
|
||||
deleteAction,
|
||||
showSignInDialog,
|
||||
addCommentTag,
|
||||
@@ -55,13 +54,9 @@ class Stream extends React.Component {
|
||||
user.suspension.until &&
|
||||
new Date(user.suspension.until) > new Date();
|
||||
|
||||
const hasOlderComments = !!(asset &&
|
||||
asset.lastComment &&
|
||||
asset.lastComment.id !== asset.comments[asset.comments.length - 1].id);
|
||||
|
||||
// Find the created_at date of the first comment. If no comments exist, set the date to a week ago.
|
||||
const firstCommentDate = asset.comments[0]
|
||||
? asset.comments[0].created_at
|
||||
const firstCommentDate = comments.nodes[0]
|
||||
? comments.nodes[0].created_at
|
||||
: new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString();
|
||||
const commentIsIgnored = (comment) => {
|
||||
return (
|
||||
@@ -137,7 +132,7 @@ class Stream extends React.Component {
|
||||
highlighted={comment.id}
|
||||
postFlag={this.props.postFlag}
|
||||
postDontAgree={this.props.postDontAgree}
|
||||
loadMore={this.props.loadMore}
|
||||
loadMore={this.props.loadNewReplies}
|
||||
deleteAction={this.props.deleteAction}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
key={highlightedComment.id}
|
||||
@@ -152,13 +147,13 @@ class Stream extends React.Component {
|
||||
<NewCount
|
||||
commentCount={asset.commentCount}
|
||||
commentCountCache={commentCountCache}
|
||||
loadMore={this.props.loadMore}
|
||||
loadMore={this.props.loadNewComments}
|
||||
firstCommentDate={firstCommentDate}
|
||||
assetId={asset.id}
|
||||
setCommentCountCache={this.props.setCommentCountCache}
|
||||
/>
|
||||
<div className="embed__stream">
|
||||
{comments.map((comment) => {
|
||||
{comments.nodes.map((comment) => {
|
||||
return commentIsIgnored(comment)
|
||||
? <IgnoredCommentTombstone key={comment.id} />
|
||||
: <Comment
|
||||
@@ -178,7 +173,7 @@ class Stream extends React.Component {
|
||||
removeCommentTag={removeCommentTag}
|
||||
ignoreUser={ignoreUser}
|
||||
commentIsIgnored={commentIsIgnored}
|
||||
loadMore={loadMore}
|
||||
loadMore={this.props.loadNewReplies}
|
||||
deleteAction={deleteAction}
|
||||
showSignInDialog={showSignInDialog}
|
||||
key={comment.id}
|
||||
@@ -193,10 +188,8 @@ class Stream extends React.Component {
|
||||
</div>
|
||||
<LoadMore
|
||||
topLevel={true}
|
||||
assetId={asset.id}
|
||||
comments={asset.comments}
|
||||
moreComments={hasOlderComments}
|
||||
loadMore={this.props.loadMore}
|
||||
moreComments={asset.comments.hasNextPage}
|
||||
loadMore={this.props.loadMoreComments}
|
||||
/>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
@@ -2,14 +2,12 @@ import React from 'react';
|
||||
import {gql, compose} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import isNil from 'lodash/isNil';
|
||||
import {NEW_COMMENT_COUNT_POLL_INTERVAL} from '../constants/stream';
|
||||
import {NEW_COMMENT_COUNT_POLL_INTERVAL, ADDTL_COMMENTS_ON_LOAD_MORE} from '../constants/stream';
|
||||
import {
|
||||
withPostComment, withPostFlag, withPostDontAgree, withDeleteAction,
|
||||
withAddCommentTag, withRemoveCommentTag, withIgnoreUser, withEditComment,
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
import update from 'immutability-helper';
|
||||
|
||||
import {notificationActions, authActions} from 'coral-framework';
|
||||
import {editName} from 'coral-framework/actions/user';
|
||||
@@ -33,80 +31,128 @@ class StreamContainer extends React.Component {
|
||||
});
|
||||
};
|
||||
|
||||
// handle paginated requests for more Comments pertaining to the Asset
|
||||
loadMore = ({limit, cursor, parent_id = null, asset_id, sort}, newComments) => {
|
||||
loadNewReplies = (parent_id) => {
|
||||
const comment = this.props.root.comment
|
||||
? this.props.root.comment.parent || this.props.root.comment // highlighted comment.
|
||||
: this.props.root.asset.comments.nodes.filter((comment) => comment.id === parent_id)[0];
|
||||
|
||||
return this.props.data.fetchMore({
|
||||
query: LOAD_MORE_QUERY,
|
||||
variables: {
|
||||
limit, // how many comments are we returning
|
||||
cursor, // the date of the first/last comment depending on the sort order
|
||||
parent_id, // if null, we're loading more top-level comments, if not, we're loading more replies to a comment
|
||||
asset_id, // the id of the asset we're currently on
|
||||
sort, // CHRONOLOGICAL or REVERSE_CHRONOLOGICAL
|
||||
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: (oldData, {fetchMoreResult:{new_top_level_comments}}) => {
|
||||
let updatedAsset;
|
||||
|
||||
if (!isNil(oldData.comment)) { // loaded replies on a highlighted (permalinked) comment
|
||||
|
||||
let comment = {};
|
||||
if (oldData.comment && oldData.comment.parent) {
|
||||
|
||||
// put comments (replies) onto the oldData.comment.parent object
|
||||
// the initial comment permalinked was a reply
|
||||
const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.parent.replies], 'id');
|
||||
comment.parent = {...oldData.comment.parent, replies: sortBy(uniqReplies, 'created_at')};
|
||||
} else if (oldData.comment) {
|
||||
|
||||
// put the comments (replies) directly onto oldData.comment
|
||||
// the initial comment permalinked was a top-level comment
|
||||
const uniqReplies = uniqBy([...new_top_level_comments, ...oldData.comment.replies], 'id');
|
||||
comment.replies = sortBy(uniqReplies, 'created_at');
|
||||
}
|
||||
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
comment: {
|
||||
...oldData.comment,
|
||||
...comment
|
||||
}
|
||||
};
|
||||
|
||||
} else if (parent_id) { // If loading more replies
|
||||
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
comments: oldData.asset.comments.map((comment) => {
|
||||
|
||||
// since the dipslayed replies and the returned replies can overlap,
|
||||
// pull out the unique ones.
|
||||
const uniqueReplies = uniqBy([...new_top_level_comments, ...comment.replies], 'id');
|
||||
|
||||
// since we just gave the returned replies precedence, they're now out of order.
|
||||
// resort according to date.
|
||||
return comment.id === parent_id
|
||||
? {...comment, replies: sortBy(uniqueReplies, 'created_at')}
|
||||
: comment;
|
||||
})
|
||||
}
|
||||
};
|
||||
} else { // If loading more top-level comments
|
||||
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
comments: newComments ? [...new_top_level_comments.reverse(), ...oldData.asset.comments]
|
||||
: [...oldData.asset.comments, ...new_top_level_comments]
|
||||
}
|
||||
};
|
||||
updateQuery: (prev, {fetchMoreResult:{comments}}) => {
|
||||
if (!comments.nodes.length) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return updatedAsset;
|
||||
}
|
||||
const updateNode = (node) =>
|
||||
update(node, {
|
||||
replies: {
|
||||
endCursor: {$set: comments.endCursor},
|
||||
nodes: {$apply: (nodes) => nodes
|
||||
.concat(comments.nodes.filter(
|
||||
(comment) => !nodes.some((node) => node.id === comment.id)
|
||||
))
|
||||
.sort(ascending)
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// highlighted comment.
|
||||
if (prev.comment) {
|
||||
if (prev.comment.parent) {
|
||||
return update(prev, {
|
||||
comment: {
|
||||
parent: {$apply: (comment) => updateNode(comment)},
|
||||
}
|
||||
});
|
||||
}
|
||||
return update(prev, {
|
||||
comment: {$apply: (comment) => updateNode(comment)},
|
||||
});
|
||||
}
|
||||
|
||||
return update(prev, {
|
||||
asset: {
|
||||
comments: {
|
||||
nodes: {
|
||||
$apply: (nodes) => nodes.map(
|
||||
(node) => node.id !== parent_id
|
||||
? node
|
||||
: updateNode(node)
|
||||
)
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
loadNewComments = () => {
|
||||
return this.props.data.fetchMore({
|
||||
query: LOAD_MORE_QUERY,
|
||||
variables: {
|
||||
limit: ADDTL_COMMENTS_ON_LOAD_MORE,
|
||||
cursor: this.props.root.asset.comments.startCursor,
|
||||
parent_id: null,
|
||||
asset_id: this.props.root.asset.id,
|
||||
sort: 'CHRONOLOGICAL',
|
||||
excludeIgnored: this.props.data.variables.excludeIgnored,
|
||||
},
|
||||
updateQuery: (prev, {fetchMoreResult:{comments}}) => {
|
||||
if (!comments.nodes.length) {
|
||||
return prev;
|
||||
}
|
||||
return update(prev, {
|
||||
asset: {
|
||||
comments: {
|
||||
startCursor: {$set: comments.endCursor},
|
||||
nodes: {$apply: (nodes) => comments.nodes.filter(
|
||||
(comment) => !nodes.some((node) => node.id === comment.id)
|
||||
)
|
||||
.concat(nodes)
|
||||
.sort(descending)
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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}}) => {
|
||||
if (!comments.nodes.length) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
return update(prev, {
|
||||
asset: {
|
||||
comments: {
|
||||
hasNextPage: {$set: comments.hasNextPage},
|
||||
endCursor: {$set: comments.endCursor},
|
||||
nodes: {$push: comments.nodes},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -127,18 +173,36 @@ class StreamContainer extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Stream {...this.props} loadMore={this.loadMore}/>;
|
||||
return <Stream
|
||||
{...this.props}
|
||||
loadMore={this.loadMore}
|
||||
loadMoreComments={this.loadMoreComments}
|
||||
loadNewComments={this.loadNewComments}
|
||||
loadNewReplies={this.loadNewReplies}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
const ascending = (a, b) => {
|
||||
const dateA = new Date(a.created_at);
|
||||
const dateB = new Date(b.created_at);
|
||||
if (dateA < dateB) { return -1; }
|
||||
if (dateA > dateB) { return 1; }
|
||||
return 0;
|
||||
};
|
||||
|
||||
const descending = (a, b) => ascending(a, b) * -1;
|
||||
|
||||
const LOAD_COMMENT_COUNTS_QUERY = gql`
|
||||
query CoralEmbedStream_LoadCommentCounts($assetUrl: String, $assetId: ID, $excludeIgnored: Boolean) {
|
||||
asset(id: $assetId, url: $assetUrl) {
|
||||
id
|
||||
commentCount(excludeIgnored: $excludeIgnored)
|
||||
comments(limit: 10) {
|
||||
id
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
nodes {
|
||||
id
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,12 +210,38 @@ const LOAD_COMMENT_COUNTS_QUERY = gql`
|
||||
|
||||
const LOAD_MORE_QUERY = gql`
|
||||
query CoralEmbedStream_LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) {
|
||||
new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies(limit: 3) {
|
||||
comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) {
|
||||
nodes {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies(limit: 3, excludeIgnored: $excludeIgnored) {
|
||||
nodes {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
}
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
${Comment.fragments.comment}
|
||||
`;
|
||||
|
||||
const commentFragment = gql`
|
||||
fragment CoralEmbedStream_Stream_comment on Comment {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies {
|
||||
nodes {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
}
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
${Comment.fragments.comment}
|
||||
@@ -161,17 +251,9 @@ const fragments = {
|
||||
root: gql`
|
||||
fragment CoralEmbedStream_Stream_root on RootQuery {
|
||||
comment(id: $commentId) @include(if: $hasComment) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
}
|
||||
...CoralEmbedStream_Stream_comment
|
||||
parent {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
}
|
||||
...CoralEmbedStream_Stream_comment
|
||||
}
|
||||
}
|
||||
asset(id: $assetId, url: $assetUrl) {
|
||||
@@ -193,17 +275,15 @@ const fragments = {
|
||||
charCount
|
||||
requireEmailConfirmation
|
||||
}
|
||||
lastComment {
|
||||
id
|
||||
}
|
||||
commentCount(excludeIgnored: $excludeIgnored)
|
||||
totalCommentCount(excludeIgnored: $excludeIgnored)
|
||||
comments(limit: 10, excludeIgnored: $excludeIgnored) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
replyCount(excludeIgnored: $excludeIgnored)
|
||||
replies(limit: 3, excludeIgnored: $excludeIgnored) {
|
||||
...${getDefinitionName(Comment.fragments.comment)}
|
||||
nodes {
|
||||
...CoralEmbedStream_Stream_comment
|
||||
}
|
||||
hasNextPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
}
|
||||
me {
|
||||
@@ -218,7 +298,7 @@ const fragments = {
|
||||
...${getDefinitionName(Comment.fragments.root)}
|
||||
}
|
||||
${Comment.fragments.root}
|
||||
${Comment.fragments.comment}
|
||||
${commentFragment}
|
||||
`,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import {add} from 'coral-framework/services/graphqlRegistry';
|
||||
import update from 'immutability-helper';
|
||||
import uuid from 'uuid/v4';
|
||||
import {insertComment, removeComment} from './utils';
|
||||
|
||||
const extension = {
|
||||
fragments: {
|
||||
EditCommentResponse: gql`
|
||||
fragment CoralEmbedStream_EditCommentResponse on EditCommentResponse {
|
||||
comment {
|
||||
id
|
||||
status
|
||||
body
|
||||
editing {
|
||||
edited
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
@@ -50,7 +57,12 @@ const extension = {
|
||||
comment {
|
||||
...CoralEmbedStream_CreateCommentResponse_Comment
|
||||
replies {
|
||||
...CoralEmbedStream_CreateCommentResponse_Comment
|
||||
nodes {
|
||||
...CoralEmbedStream_CreateCommentResponse_Comment
|
||||
}
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,114 +140,26 @@ const extension = {
|
||||
action_summaries: [],
|
||||
tags,
|
||||
status: null,
|
||||
id: 'pending'
|
||||
id: `pending-${uuid()}`,
|
||||
}
|
||||
}
|
||||
},
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previousData, {mutationResult: {data: {createComment: {comment}}}}) => {
|
||||
if (previousData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
|
||||
return previousData;
|
||||
CoralEmbedStream_Embed: (prev, {mutationResult: {data: {createComment: {comment}}}}) => {
|
||||
if (prev.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
|
||||
return prev;
|
||||
}
|
||||
|
||||
let updatedAsset;
|
||||
|
||||
// If posting a reply
|
||||
if (parent_id) {
|
||||
updatedAsset = {
|
||||
...previousData,
|
||||
asset: {
|
||||
...previousData.asset,
|
||||
comments: previousData.asset.comments.map((oldComment) => {
|
||||
return oldComment.id === parent_id
|
||||
? {...oldComment, replies: [...oldComment.replies, comment], replyCount: oldComment.replyCount + 1}
|
||||
: oldComment;
|
||||
})
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
// If posting a top-level comment
|
||||
updatedAsset = {
|
||||
...previousData,
|
||||
asset: {
|
||||
...previousData.asset,
|
||||
commentCount: previousData.asset.commentCount + 1,
|
||||
comments: [comment, ...previousData.asset.comments]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return updatedAsset;
|
||||
}
|
||||
return insertComment(prev, parent_id, comment);
|
||||
},
|
||||
}
|
||||
}),
|
||||
EditComment: ({
|
||||
variables: {id, edit},
|
||||
}) => ({
|
||||
EditComment: () => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previousData, {mutationResult: {data: {editComment: {comment}}}}) => {
|
||||
const {status} = comment;
|
||||
const updateCommentWithEdit = (comment, edit) => {
|
||||
const {body} = edit;
|
||||
const editedComment = update(comment, {
|
||||
$merge: {
|
||||
body
|
||||
},
|
||||
editing: {$merge:{edited:true}}
|
||||
});
|
||||
return editedComment;
|
||||
};
|
||||
const commentIsStillVisible = (comment) => {
|
||||
return !((id === comment.id) && (['PREMOD', 'REJECTED'].includes(status)));
|
||||
};
|
||||
const resultReflectingEdit = update(previousData, {
|
||||
asset: {
|
||||
comments: {
|
||||
$apply: (comments) => {
|
||||
return comments.filter(commentIsStillVisible).map((comment) => {
|
||||
let replyWasEditedToBeHidden = false;
|
||||
if (comment.id === id) {
|
||||
return updateCommentWithEdit(comment, edit);
|
||||
}
|
||||
const commentWithUpdatedReplies = update(comment, {
|
||||
replies: {
|
||||
$apply: (comments) => {
|
||||
return comments
|
||||
.filter((c) => {
|
||||
if (commentIsStillVisible(c)) {
|
||||
return true;
|
||||
}
|
||||
replyWasEditedToBeHidden = true;
|
||||
return false;
|
||||
})
|
||||
.map((comment) => {
|
||||
if (comment.id === id) {
|
||||
return updateCommentWithEdit(comment, edit);
|
||||
}
|
||||
return comment;
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// If a reply was edited to be hdiden, then this parent needs its replyCount to be decremented.
|
||||
if (replyWasEditedToBeHidden) {
|
||||
return update(commentWithUpdatedReplies, {
|
||||
replyCount: {
|
||||
$apply: (replyCount) => {
|
||||
return replyCount - 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return commentWithUpdatedReplies;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return resultReflectingEdit;
|
||||
CoralEmbedStream_Embed: (prev, {mutationResult: {data: {editComment: {comment}}}}) => {
|
||||
if (!['PREMOD', 'REJECTED'].includes(comment.status)) {
|
||||
return null;
|
||||
}
|
||||
return removeComment(prev, comment.id);
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import update from 'immutability-helper';
|
||||
|
||||
function findAndInsertComment(parent, id, comment) {
|
||||
const [connectionField, countField, action] = parent.comments
|
||||
? ['comments', 'commentCount', '$unshift']
|
||||
: ['replies', 'replyCount', '$push'];
|
||||
|
||||
if (!id || parent.id === id) {
|
||||
return update(parent, {
|
||||
[connectionField]: {
|
||||
nodes: {[action]: [comment]},
|
||||
},
|
||||
[countField]: {$apply: (c) => c + 1},
|
||||
});
|
||||
}
|
||||
const connection = parent[connectionField];
|
||||
if (!connection) {
|
||||
return parent;
|
||||
}
|
||||
return update(parent, {
|
||||
[connectionField]: {
|
||||
nodes: {
|
||||
$apply: (nodes) =>
|
||||
nodes.map((node) => findAndInsertComment(node, id, comment))
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function insertComment(root, id, comment) {
|
||||
if (root.comment) {
|
||||
if (root.comment.parent) {
|
||||
return update(root, {
|
||||
comment: {
|
||||
parent: {
|
||||
$apply: (node) => findAndInsertComment(node, id, comment),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return update(root, {
|
||||
comment: {
|
||||
$apply: (node) => findAndInsertComment(node, id, comment),
|
||||
},
|
||||
});
|
||||
}
|
||||
return update(root, {
|
||||
asset: {$apply: (asset) => findAndInsertComment(asset, id, comment)},
|
||||
});
|
||||
}
|
||||
|
||||
function findAndRemoveComment(parent, id) {
|
||||
const [connectionField, countField] = parent.comments
|
||||
? ['comments', 'commentCount']
|
||||
: ['replies', 'replyCount'];
|
||||
|
||||
const connection = parent[connectionField];
|
||||
if (!connection) {
|
||||
return parent;
|
||||
}
|
||||
|
||||
let next = connection.nodes.filter((node) => node.id !== id);
|
||||
if (next.length === connection.nodes.length) {
|
||||
next = next.map((node) => findAndRemoveComment(node, id));
|
||||
}
|
||||
let changes = {
|
||||
[connectionField]: {
|
||||
nodes: {$set: next},
|
||||
},
|
||||
};
|
||||
|
||||
if (parent[countField] && next.length !== connection.nodes.length) {
|
||||
changes[countField] = {$set: changes[countField] - 1};
|
||||
}
|
||||
return update(parent, changes);
|
||||
}
|
||||
|
||||
export function removeComment(root, id, comment) {
|
||||
if (root.comment) {
|
||||
if (root.comment.parent) {
|
||||
return update(root, {
|
||||
comment: {
|
||||
parent: {
|
||||
$apply: (node) => findAndRemoveComment(node, id, comment),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return update(root, {
|
||||
comment: {
|
||||
$apply: (node) => findAndRemoveComment(node, id, comment),
|
||||
},
|
||||
});
|
||||
}
|
||||
return update(root, {
|
||||
asset: {$apply: (asset) => findAndRemoveComment(asset, id, comment)},
|
||||
});
|
||||
}
|
||||
@@ -72,8 +72,8 @@ class ProfileContainer extends Component {
|
||||
<hr />
|
||||
|
||||
<h3>{t('framework.my_comments')}</h3>
|
||||
{me.comments.length
|
||||
? <CommentHistory comments={me.comments} asset={asset} link={link} />
|
||||
{me.comments.nodes.length
|
||||
? <CommentHistory comments={me.comments.nodes} asset={asset} link={link} />
|
||||
: <p>{t('user_no_comment')}</p>}
|
||||
</div>
|
||||
);
|
||||
@@ -90,14 +90,16 @@ const withQuery = graphql(
|
||||
username,
|
||||
}
|
||||
comments {
|
||||
id
|
||||
body
|
||||
asset {
|
||||
nodes {
|
||||
id
|
||||
title
|
||||
url
|
||||
body
|
||||
asset {
|
||||
id
|
||||
title
|
||||
url
|
||||
}
|
||||
created_at
|
||||
}
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
@@ -49,7 +49,8 @@ const getAssetsForMetrics = async ({loaders: {Actions, Comments}}) => {
|
||||
|
||||
const ids = actions.map(({item_id}) => item_id);
|
||||
|
||||
return Comments.getByQuery({ids});
|
||||
return Comments.getByQuery({ids})
|
||||
.then((connection) => connection.nodes);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -293,9 +293,24 @@ const getCommentsByQuery = async ({user}, {ids, statuses, asset_id, parent_id, a
|
||||
}
|
||||
}
|
||||
|
||||
return comments
|
||||
.sort({created_at: sort === 'REVERSE_CHRONOLOGICAL' ? -1 : 1})
|
||||
.limit(limit);
|
||||
let query = comments
|
||||
.sort({created_at: sort === 'REVERSE_CHRONOLOGICAL' ? -1 : 1});
|
||||
if (limit) {
|
||||
query = query.limit(limit + 1);
|
||||
}
|
||||
return query.then((nodes) => {
|
||||
let hasNextPage = false;
|
||||
if (limit && nodes.length > limit) {
|
||||
hasNextPage = true;
|
||||
nodes.splice(limit, 1);
|
||||
}
|
||||
return Promise.resolve({
|
||||
startCursor: nodes.length ? nodes[0].created_at : null,
|
||||
endCursor: nodes.length ? nodes[nodes.length - 1].created_at : null,
|
||||
hasNextPage,
|
||||
nodes,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -338,9 +338,9 @@ const edit = async (context, {id, asset_id, edit: {body}}) => {
|
||||
const status = await resolveNewCommentStatus(context, {asset_id, body}, wordlist, settings);
|
||||
|
||||
// Execute the edit.
|
||||
await CommentsService.edit(id, context.user.id, {body, status});
|
||||
const comment = await CommentsService.edit(id, context.user.id, {body, status});
|
||||
|
||||
return {status};
|
||||
return comment;
|
||||
};
|
||||
|
||||
module.exports = (context) => {
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
const Asset = {
|
||||
lastComment({id}, _, {loaders: {Comments}}) {
|
||||
return Comments.getByQuery({
|
||||
asset_id: id,
|
||||
limit: 1,
|
||||
parent_id: null
|
||||
}).then((data) => data[0]);
|
||||
},
|
||||
recentComments({id}, _, {loaders: {Comments}}) {
|
||||
return Comments.genRecentComments.load(id);
|
||||
},
|
||||
|
||||
+11
-7
@@ -76,7 +76,7 @@ type User {
|
||||
ignoredUsers: [User!]
|
||||
|
||||
# returns all comments based on a query.
|
||||
comments(query: CommentsQuery): [Comment!]
|
||||
comments(query: CommentsQuery): CommentConnection!
|
||||
|
||||
# reliable is the reference to a given user's Reliability. If the requesting
|
||||
# user does not have permission to access the reliability, null will be
|
||||
@@ -229,7 +229,7 @@ type Comment {
|
||||
recentReplies: [Comment!]
|
||||
|
||||
# the replies that were made to the comment.
|
||||
replies(sort: SORT_ORDER = CHRONOLOGICAL, limit: Int = 3, excludeIgnored: Boolean): [Comment!]
|
||||
replies(sort: SORT_ORDER = CHRONOLOGICAL, limit: Int = 3, excludeIgnored: Boolean): CommentConnection!
|
||||
|
||||
# The count of replies on a comment.
|
||||
replyCount(excludeIgnored: Boolean): Int
|
||||
@@ -253,6 +253,13 @@ type Comment {
|
||||
editing: EditInfo
|
||||
}
|
||||
|
||||
type CommentConnection {
|
||||
hasNextPage: Boolean!
|
||||
startCursor: Date
|
||||
endCursor: Date
|
||||
nodes: [Comment!]!
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Actions
|
||||
################################################################################
|
||||
@@ -462,14 +469,11 @@ type Asset {
|
||||
# The URL that the asset is located on.
|
||||
url: String
|
||||
|
||||
# Returns last comment
|
||||
lastComment: Comment
|
||||
|
||||
# Returns recent comments
|
||||
recentComments: [Comment!]
|
||||
|
||||
# The top level comments that are attached to the asset.
|
||||
comments(sort: SORT_ORDER = REVERSE_CHRONOLOGICAL, limit: Int = 10, excludeIgnored: Boolean): [Comment!]
|
||||
comments(sort: SORT_ORDER = REVERSE_CHRONOLOGICAL, limit: Int = 10, excludeIgnored: Boolean): CommentConnection!
|
||||
|
||||
# The count of top level comments on the asset.
|
||||
commentCount(excludeIgnored: Boolean): Int
|
||||
@@ -576,7 +580,7 @@ type RootQuery {
|
||||
asset(id: ID, url: String): Asset
|
||||
|
||||
# Comments returned based on a query.
|
||||
comments(query: CommentsQuery!): [Comment!]
|
||||
comments(query: CommentsQuery!): CommentConnection
|
||||
|
||||
# Return the count of comments satisfied by the query. Note that this edge is
|
||||
# expensive as it is not batched. Requires the `ADMIN` role.
|
||||
|
||||
@@ -45,16 +45,25 @@ describe('graph.queries.asset', () => {
|
||||
query assetCommentsQuery($id: ID!) {
|
||||
asset(id: $id) {
|
||||
comments(limit: 10) {
|
||||
id
|
||||
body
|
||||
nodes {
|
||||
id
|
||||
body
|
||||
created_at
|
||||
}
|
||||
startCursor
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const res = await graphql(schema, assetCommentsQuery, {}, context, {id: asset.id});
|
||||
expect(res.erros).is.empty;
|
||||
const comments = res.data.asset.comments;
|
||||
expect(comments.length).to.equal(2);
|
||||
const {nodes, startCursor, endCursor, hasNextPage} = res.data.asset.comments;
|
||||
expect(nodes.length).to.equal(2);
|
||||
expect(startCursor).to.equal(nodes[0].created_at);
|
||||
expect(endCursor).to.equal(nodes[1].created_at);
|
||||
expect(hasNextPage).to.be.false;
|
||||
});
|
||||
|
||||
it('can query comments edge to exclude comments ignored by user', async () => {
|
||||
@@ -65,7 +74,7 @@ describe('graph.queries.asset', () => {
|
||||
asset_id: asset.id,
|
||||
body: `hello there! ${String(Math.random()).slice(2)}`,
|
||||
})));
|
||||
|
||||
|
||||
// Add the second user to the list of ignored users.
|
||||
context.user.ignoresUsers.push(users[1].id);
|
||||
|
||||
@@ -73,8 +82,10 @@ describe('graph.queries.asset', () => {
|
||||
query assetCommentsQuery($id: ID!, $url: String!, $excludeIgnored: Boolean!) {
|
||||
asset(id: $id, url: $url) {
|
||||
comments(limit: 10, excludeIgnored: $excludeIgnored) {
|
||||
id
|
||||
body
|
||||
nodes {
|
||||
id
|
||||
body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,8 +101,8 @@ describe('graph.queries.asset', () => {
|
||||
console.error(res.errors);
|
||||
}
|
||||
expect(res.errors).is.empty;
|
||||
const comments = res.data.asset.comments;
|
||||
expect(comments.length).to.equal(1);
|
||||
const nodes = res.data.asset.comments.nodes;
|
||||
expect(nodes.length).to.equal(1);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user