mirror of
https://github.com/wassname/talk.git
synced 2026-07-09 21:36:00 +08:00
Merge pull request #567 from coralproject/query-mutation-api
Extendable GraphQL framework
This commit is contained in:
@@ -65,7 +65,7 @@ class Comment extends React.Component {
|
||||
parentId: PropTypes.string,
|
||||
highlighted: PropTypes.string,
|
||||
addNotification: PropTypes.func.isRequired,
|
||||
postItem: PropTypes.func.isRequired,
|
||||
postComment: PropTypes.func.isRequired,
|
||||
depth: PropTypes.number.isRequired,
|
||||
asset: PropTypes.shape({
|
||||
id: PropTypes.string,
|
||||
@@ -145,7 +145,7 @@ class Comment extends React.Component {
|
||||
|
||||
// re-render
|
||||
this.setState(this.state);
|
||||
}, msLeftToEdit);
|
||||
}, msLeftToEdit);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
@@ -161,7 +161,7 @@ class Comment extends React.Component {
|
||||
currentUser,
|
||||
asset,
|
||||
depth,
|
||||
postItem,
|
||||
postComment,
|
||||
addNotification,
|
||||
showSignInDialog,
|
||||
highlighted,
|
||||
@@ -286,7 +286,7 @@ class Comment extends React.Component {
|
||||
</span>
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
this.state.isEditing
|
||||
? <EditableCommentContent
|
||||
editComment={this.props.editComment.bind(null, comment.id, asset.id)}
|
||||
@@ -370,7 +370,7 @@ class Comment extends React.Component {
|
||||
parentId={parentId || comment.id}
|
||||
addNotification={addNotification}
|
||||
authorId={currentUser.id}
|
||||
postItem={postItem}
|
||||
postComment={postComment}
|
||||
assetId={asset.id}
|
||||
/>
|
||||
: null}
|
||||
@@ -386,7 +386,7 @@ class Comment extends React.Component {
|
||||
activeReplyBox={activeReplyBox}
|
||||
addNotification={addNotification}
|
||||
parentId={comment.id}
|
||||
postItem={postItem}
|
||||
postComment={postComment}
|
||||
editComment={this.props.editComment}
|
||||
depth={depth + 1}
|
||||
asset={asset}
|
||||
@@ -430,7 +430,7 @@ function commentIsStillEditable (comment) {
|
||||
if ( ! editing) {return false;}
|
||||
const editableUntil = getEditableUntilDate(comment);
|
||||
const editWindowExpired = (editableUntil - new Date) < 0;
|
||||
return ! editWindowExpired;
|
||||
return ! editWindowExpired;
|
||||
}
|
||||
|
||||
// return number of milliseconds before edit window expires
|
||||
|
||||
@@ -26,7 +26,7 @@ class Stream extends React.Component {
|
||||
render() {
|
||||
const {
|
||||
root: {asset, asset: {comments}, comment, myIgnoredUsers},
|
||||
postItem,
|
||||
postComment,
|
||||
addNotification,
|
||||
postFlag,
|
||||
postDontAgree,
|
||||
@@ -84,7 +84,7 @@ class Stream extends React.Component {
|
||||
{user
|
||||
? <CommentBox
|
||||
addNotification={this.props.addNotification}
|
||||
postItem={this.props.postItem}
|
||||
postComment={this.props.postComment}
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
setCommentCountCache={this.props.setCommentCountCache}
|
||||
@@ -122,7 +122,7 @@ class Stream extends React.Component {
|
||||
activeReplyBox={this.props.activeReplyBox}
|
||||
addNotification={addNotification}
|
||||
depth={0}
|
||||
postItem={this.props.postItem}
|
||||
postComment={this.props.postComment}
|
||||
asset={asset}
|
||||
currentUser={user}
|
||||
highlighted={comment.id}
|
||||
@@ -161,7 +161,7 @@ class Stream extends React.Component {
|
||||
activeReplyBox={this.props.activeReplyBox}
|
||||
addNotification={addNotification}
|
||||
depth={0}
|
||||
postItem={postItem}
|
||||
postComment={postComment}
|
||||
asset={asset}
|
||||
currentUser={user}
|
||||
postFlag={postFlag}
|
||||
@@ -198,7 +198,7 @@ class Stream extends React.Component {
|
||||
|
||||
Stream.propTypes = {
|
||||
addNotification: PropTypes.func.isRequired,
|
||||
postItem: PropTypes.func.isRequired,
|
||||
postComment: PropTypes.func.isRequired,
|
||||
|
||||
// dispatch action to add a tag to a comment
|
||||
addCommentTag: PropTypes.func,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import Comment from '../components/Comment';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {withFragments} from 'coral-framework/hocs';
|
||||
import {getSlotsFragments} from 'coral-framework/helpers/plugins';
|
||||
|
||||
const pluginFragments = getSlotsFragments([
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
import React from 'react';
|
||||
import {compose, gql, graphql} from 'react-apollo';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
import branch from 'recompose/branch';
|
||||
import renderComponent from 'recompose/renderComponent';
|
||||
import update from 'immutability-helper';
|
||||
|
||||
import {Spinner} from 'coral-ui';
|
||||
import {authActions, assetActions, pym} from 'coral-framework';
|
||||
|
||||
import Stream from './Stream';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import {withQuery} from 'coral-framework/hocs';
|
||||
import Embed from '../components/Embed';
|
||||
import Stream from './Stream';
|
||||
|
||||
import {setActiveTab} from '../actions/embed';
|
||||
import {setCommentCountCache, viewAllComments} from '../actions/stream';
|
||||
import {getDefinitionName, separateDataAndRoot} from 'coral-framework/utils';
|
||||
|
||||
const {logout, checkLogin} = authActions;
|
||||
const {fetchAssetSuccess} = assetActions;
|
||||
@@ -74,7 +73,7 @@ const EMBED_QUERY = gql`
|
||||
${Stream.fragments.root}
|
||||
`;
|
||||
|
||||
export const withQuery = graphql(EMBED_QUERY, {
|
||||
export const withEmbedQuery = withQuery(EMBED_QUERY, {
|
||||
options: ({auth, commentId, assetId, assetUrl}) => ({
|
||||
variables: {
|
||||
assetId,
|
||||
@@ -83,11 +82,7 @@ export const withQuery = graphql(EMBED_QUERY, {
|
||||
hasComment: commentId !== '',
|
||||
excludeIgnored: Boolean(auth && auth.user && auth.user.id),
|
||||
},
|
||||
reducer: (previousResult, action, variables) => {
|
||||
return reduceEditCommentActionsToUpdateStreamQuery(previousResult, action, variables);
|
||||
},
|
||||
}),
|
||||
props: ({data}) => separateDataAndRoot(data)
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
@@ -116,88 +111,6 @@ const mapDispatchToProps = dispatch =>
|
||||
export default compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
branch(props => !props.auth.checkedInitialLogin && props.config, renderComponent(Spinner)),
|
||||
withQuery
|
||||
withEmbedQuery,
|
||||
)(EmbedContainer);
|
||||
|
||||
/**
|
||||
* Reduce editComment mutation actions
|
||||
* producing a new queryStream result where asset.comments reflects the edit
|
||||
*/
|
||||
function reduceEditCommentActionsToUpdateStreamQuery(previousResult, action) {
|
||||
if ( ! (action.type === 'APOLLO_MUTATION_RESULT' && action.operationName === 'editComment')) {
|
||||
return previousResult;
|
||||
}
|
||||
const resultHasErrors = (result) => {
|
||||
try {
|
||||
return result.data.editComment.errors.length > 0;
|
||||
} catch (error) {
|
||||
|
||||
// expected if no errors;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (resultHasErrors(action.result)) {
|
||||
return previousResult;
|
||||
}
|
||||
const {variables: {id, edit}, result: {data: {editComment: {comment: {status}}}}} = action;
|
||||
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(previousResult, {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,17 @@ 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 {postComment, postFlag, postDontAgree, deleteAction, addCommentTag, removeCommentTag, ignoreUser, editComment} from 'coral-framework/graphql/mutations';
|
||||
import {
|
||||
withPostComment, withPostFlag, withPostDontAgree, withDeleteAction,
|
||||
withAddCommentTag, withRemoveCommentTag, withIgnoreUser, withEditComment,
|
||||
} from 'coral-framework/graphql/mutations';
|
||||
|
||||
import {notificationActions, authActions} from 'coral-framework';
|
||||
import {editName} from 'coral-framework/actions/user';
|
||||
import {setCommentCountCache, setActiveReplyBox} from '../actions/stream';
|
||||
import Stream from '../components/Stream';
|
||||
import Comment from './Comment';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {withFragments} from 'coral-framework/hocs';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
|
||||
const {showSignInDialog} = authActions;
|
||||
@@ -236,13 +240,13 @@ const mapDispatchToProps = dispatch =>
|
||||
export default compose(
|
||||
withFragments(fragments),
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
postComment,
|
||||
postFlag,
|
||||
postDontAgree,
|
||||
addCommentTag,
|
||||
removeCommentTag,
|
||||
ignoreUser,
|
||||
deleteAction,
|
||||
editComment,
|
||||
withPostComment,
|
||||
withPostFlag,
|
||||
withPostDontAgree,
|
||||
withAddCommentTag,
|
||||
withRemoveCommentTag,
|
||||
withIgnoreUser,
|
||||
withDeleteAction,
|
||||
withEditComment,
|
||||
)(StreamContainer);
|
||||
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import {add} from 'coral-framework/services/graphqlRegistry';
|
||||
import update from 'immutability-helper';
|
||||
|
||||
const extension = {
|
||||
fragments: {
|
||||
EditCommentResponse: gql`
|
||||
fragment CoralEmbedStream_EditCommentResponse on EditCommentResponse {
|
||||
comment {
|
||||
status
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
StopIgnoringUserResponse: gql`
|
||||
fragment CoralEmbedStream_StopIgnoringUserResponse on StopIgnoringUserResponse {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
IgnoreUserResponse: gql`
|
||||
fragment CoralEmbedStream_IgnoreUserResponse on IgnoreUserResponse {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
RemoveCommentTagResponse: gql`
|
||||
fragment CoralEmbedStream_RemoveCommentTagResponse on RemoveCommentTagResponse {
|
||||
comment {
|
||||
id
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
AddCommentTagResponse: gql`
|
||||
fragment CoralEmbedStream_AddCommentTagResponse on AddCommentTagResponse {
|
||||
comment {
|
||||
id
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
DeleteActionResponse: gql`
|
||||
fragment CoralEmbedStream_DeleteActionResponse on DeleteActionResponse {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
CreateFlagResponse: gql`
|
||||
fragment CoralEmbedStream_CreateFlagResponse on CreateFlagResponse {
|
||||
flag {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
CreateDontAgreeResponse : gql`
|
||||
fragment CoralEmbedStream_CreateDontAgreeResponse on CreateDontAgreeResponse {
|
||||
dontagree {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
`,
|
||||
CreateCommentResponse: gql`
|
||||
fragment CoralEmbedStream_CreateCommentResponse on CreateCommentResponse {
|
||||
comment {
|
||||
...CoralEmbedStream_CreateCommentResponse_Comment
|
||||
replies {
|
||||
...CoralEmbedStream_CreateCommentResponse_Comment
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
|
||||
fragment CoralEmbedStream_CreateCommentResponse_Comment on Comment {
|
||||
id
|
||||
body
|
||||
created_at
|
||||
status
|
||||
replyCount
|
||||
tags {
|
||||
name
|
||||
}
|
||||
user {
|
||||
id
|
||||
name: username
|
||||
}
|
||||
action_summaries {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
created_at
|
||||
}
|
||||
}
|
||||
editing {
|
||||
edited
|
||||
editableUntil
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
mutations: {
|
||||
IgnoreUser: () => ({
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
],
|
||||
}),
|
||||
StopIgnoringUser: () => ({
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
],
|
||||
}),
|
||||
PostComment: ({
|
||||
variables: {comment: {asset_id, body, parent_id, tags = []}},
|
||||
state: {auth},
|
||||
}) => ({
|
||||
optimisticResponse: {
|
||||
createComment: {
|
||||
comment: {
|
||||
user: {
|
||||
id: auth.toJS().user.id,
|
||||
name: auth.toJS().user.username
|
||||
},
|
||||
created_at: new Date().toISOString(),
|
||||
body,
|
||||
parent_id,
|
||||
asset_id,
|
||||
action_summaries: [],
|
||||
tags,
|
||||
status: null,
|
||||
id: 'pending'
|
||||
}
|
||||
}
|
||||
},
|
||||
updateQueries: {
|
||||
EmbedQuery: (previousData, {mutationResult: {data: {createComment: {comment}}}}) => {
|
||||
if (previousData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
|
||||
return previousData;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}),
|
||||
EditComment: ({
|
||||
variables: {id, edit},
|
||||
}) => ({
|
||||
updateQueries: {
|
||||
EmbedQuery: (previousData, {mutationResult: {data: {editComment: {comment: {status}}}}}) => {
|
||||
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;
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
add(extension);
|
||||
@@ -4,6 +4,7 @@ import {ApolloProvider} from 'react-apollo';
|
||||
|
||||
import {client} from 'coral-framework/services/client';
|
||||
import {checkLogin} from 'coral-framework/actions/auth';
|
||||
import './graphql';
|
||||
import {addExternalConfig} from 'coral-embed-stream/src/actions/config';
|
||||
|
||||
import reducers from './reducers';
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// fragments defined here are automatically registered.
|
||||
export default {};
|
||||
@@ -1,8 +0,0 @@
|
||||
fragment actionSummaryView on ActionSummary {
|
||||
__typename
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
created_at
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#import "../fragments/actionSummaryView.graphql"
|
||||
|
||||
fragment commentView on Comment {
|
||||
id
|
||||
body
|
||||
created_at
|
||||
status
|
||||
tags {
|
||||
name
|
||||
}
|
||||
user {
|
||||
id
|
||||
name: username
|
||||
}
|
||||
action_summaries {
|
||||
...actionSummaryView
|
||||
}
|
||||
editing {
|
||||
edited
|
||||
editableUntil
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import withMutation from '../hocs/withMutation';
|
||||
|
||||
export const withPostComment = withMutation(
|
||||
gql`
|
||||
mutation PostComment($comment: CreateCommentInput!) {
|
||||
createComment(comment: $comment) {
|
||||
...CreateCommentResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
postComment: comment => {
|
||||
return mutate({
|
||||
variables: {
|
||||
comment
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const withEditComment = withMutation(
|
||||
gql`
|
||||
mutation EditComment($id: ID!, $asset_id: ID!, $edit: EditCommentInput) {
|
||||
editComment(id:$id, asset_id:$asset_id, edit:$edit) {
|
||||
...EditCommentResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
editComment: (id, asset_id, edit) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
asset_id,
|
||||
edit,
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const withPostFlag = withMutation(
|
||||
gql`
|
||||
mutation PostFlag($flag: CreateFlagInput!) {
|
||||
createFlag(flag: $flag) {
|
||||
...CreateFlagResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
postFlag: (flag) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
flag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withPostDontAgree = withMutation(
|
||||
gql`
|
||||
mutation CreateDontAgree($dontagree: CreateDontAgreeInput!) {
|
||||
createDontAgree(dontagree: $dontagree) {
|
||||
...CreateDontAgreeResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
postDontAgree: (dontagree) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
dontagree
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withDeleteAction = withMutation(
|
||||
gql`
|
||||
mutation DeleteAction($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
...DeleteActionResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
deleteAction: (id) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withAddCommentTag = withMutation(
|
||||
gql`
|
||||
mutation AddCommentTag($id: ID!, $tag: String!) {
|
||||
addCommentTag(id:$id, tag:$tag) {
|
||||
...AddCommentTagResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
addCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withRemoveCommentTag = withMutation(
|
||||
gql`
|
||||
mutation RemoveCommentTag($id: ID!, $tag: String!) {
|
||||
removeCommentTag(id:$id, tag:$tag) {
|
||||
...RemoveCommentTagResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
removeCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withIgnoreUser = withMutation(
|
||||
gql`
|
||||
mutation IgnoreUser($id: ID!) {
|
||||
ignoreUser(id:$id) {
|
||||
...IgnoreUserResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
ignoreUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withStopIgnoringUser = withMutation(
|
||||
gql`
|
||||
mutation StopIgnoringUser($id: ID!) {
|
||||
stopIgnoringUser(id:$id) {
|
||||
...StopIgnoringUserResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
stopIgnoringUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
mutation AddCommentTag ($id: ID!, $tag: String!) {
|
||||
addCommentTag(id:$id, tag:$tag) {
|
||||
comment {
|
||||
id
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
mutation deleteAction ($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
mutation editComment ($id: ID!, $asset_id: ID!, $edit: EditCommentInput) {
|
||||
editComment(id:$id, asset_id:$asset_id, edit:$edit) {
|
||||
comment {
|
||||
status
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
mutation ignoreUser ($id: ID!) {
|
||||
ignoreUser(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
import {graphql} from 'react-apollo';
|
||||
import POST_COMMENT from './postComment.graphql';
|
||||
import POST_FLAG from './postFlag.graphql';
|
||||
import POST_DONT_AGREE from './postDontAgree.graphql';
|
||||
import DELETE_ACTION from './deleteAction.graphql';
|
||||
import ADD_COMMENT_TAG from './addCommentTag.graphql';
|
||||
import REMOVE_COMMENT_TAG from './removeCommentTag.graphql';
|
||||
import IGNORE_USER from './ignoreUser.graphql';
|
||||
import STOP_IGNORING_USER from './stopIgnoringUser.graphql';
|
||||
import EDIT_COMMENT from './editComment.graphql';
|
||||
|
||||
import commentView from '../fragments/commentView.graphql';
|
||||
|
||||
export const postComment = graphql(POST_COMMENT, {
|
||||
options: () => ({
|
||||
fragments: commentView
|
||||
}),
|
||||
props: ({ownProps, mutate}) => ({
|
||||
postItem: comment => {
|
||||
const {asset_id, body, parent_id, tags = []} = comment;
|
||||
return mutate({
|
||||
variables: {
|
||||
comment
|
||||
},
|
||||
optimisticResponse: {
|
||||
createComment: {
|
||||
comment: {
|
||||
user: {
|
||||
id: ownProps.auth.user.id,
|
||||
name: ownProps.auth.user.username
|
||||
},
|
||||
created_at: new Date().toISOString(),
|
||||
body,
|
||||
parent_id,
|
||||
asset_id,
|
||||
action_summaries: [],
|
||||
tags,
|
||||
status: null,
|
||||
id: 'pending'
|
||||
}
|
||||
}
|
||||
},
|
||||
updateQueries: {
|
||||
EmbedQuery: (oldData, {mutationResult: {data: {createComment: {comment}}}}) => {
|
||||
|
||||
if (oldData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
|
||||
return oldData;
|
||||
}
|
||||
|
||||
let updatedAsset;
|
||||
|
||||
// If posting a reply
|
||||
if (parent_id) {
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
comments: oldData.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 = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
commentCount: oldData.asset.commentCount + 1,
|
||||
comments: [comment, ...oldData.asset.comments]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return updatedAsset;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const postFlag = graphql(POST_FLAG, {
|
||||
props: ({mutate}) => ({
|
||||
postFlag: (flag) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
flag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const postDontAgree = graphql(POST_DONT_AGREE, {
|
||||
props: ({mutate}) => ({
|
||||
postDontAgree: (dontagree) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
dontagree
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const deleteAction = graphql(DELETE_ACTION, {
|
||||
props: ({mutate}) => ({
|
||||
deleteAction: (id) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const addCommentTag = graphql(ADD_COMMENT_TAG, {
|
||||
props: ({mutate}) => ({
|
||||
addCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const removeCommentTag = graphql(REMOVE_COMMENT_TAG, {
|
||||
props: ({mutate}) => ({
|
||||
removeCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
export const ignoreUser = graphql(IGNORE_USER, {
|
||||
props: ({mutate}) => ({
|
||||
ignoreUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
]
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
export const stopIgnoringUser = graphql(STOP_IGNORING_USER, {
|
||||
props: ({mutate}) => {
|
||||
return {
|
||||
stopIgnoringUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const editComment = graphql(EDIT_COMMENT, {
|
||||
props: ({mutate}) => {
|
||||
return {
|
||||
editComment: (id, asset_id, edit) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
asset_id,
|
||||
edit,
|
||||
},
|
||||
refetchQueries: []
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
#import "../fragments/commentView.graphql"
|
||||
|
||||
mutation CreateComment ($comment: CreateCommentInput!) {
|
||||
createComment(comment: $comment) {
|
||||
comment {
|
||||
...commentView
|
||||
replyCount
|
||||
replies {
|
||||
...commentView
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
mutation CreateDontAgree($dontagree: CreateDontAgreeInput!) {
|
||||
createDontAgree(dontagree:$dontagree) {
|
||||
dontagree {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
mutation CreateFlag($flag: CreateFlagInput!) {
|
||||
createFlag(flag:$flag) {
|
||||
flag {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
mutation RemoveCommentTag ($id: ID!, $tag: String!) {
|
||||
removeCommentTag(id:$id, tag:$tag) {
|
||||
comment {
|
||||
id
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
mutation stopIgnoringUser ($id: ID!) {
|
||||
stopIgnoringUser(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import {graphql} from 'react-apollo';
|
||||
import MY_COMMENT_HISTORY from './myCommentHistory.graphql';
|
||||
import MY_IGNORED_USERS from './myIgnoredUsers.graphql';
|
||||
|
||||
export const myCommentHistory = graphql(MY_COMMENT_HISTORY, {});
|
||||
|
||||
export const myIgnoredUsers = graphql(MY_IGNORED_USERS, {
|
||||
props: ({data}) => {
|
||||
return ({
|
||||
myIgnoredUsersData: data
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
query myCommentHistory {
|
||||
me {
|
||||
comments {
|
||||
id
|
||||
body
|
||||
asset {
|
||||
id
|
||||
title
|
||||
url
|
||||
}
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
query myIgnoredUsers {
|
||||
myIgnoredUsers {
|
||||
id,
|
||||
username,
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ import merge from 'lodash/merge';
|
||||
import flatten from 'lodash/flatten';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
import uniq from 'lodash/uniq';
|
||||
import pick from 'lodash/pick';
|
||||
import plugins from 'pluginsConfig';
|
||||
import {gql} from 'react-apollo';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
|
||||
|
||||
export const pluginReducers = merge(
|
||||
...plugins
|
||||
@@ -25,19 +25,28 @@ export function getSlotElements(slot, props = {}) {
|
||||
}
|
||||
|
||||
function getComponentFragments(components) {
|
||||
return components
|
||||
const res = components
|
||||
.map(c => c.fragments)
|
||||
.filter(fragments => fragments)
|
||||
.reduce((res, fragments) => {
|
||||
Object.keys(fragments).forEach(key => {
|
||||
if (!(key in res)) {
|
||||
res[key] = {spreads: '', definitions: ''};
|
||||
res[key] = {spreads: [], definitions: []};
|
||||
}
|
||||
res[key].spreads += `...${getDefinitionName(fragments[key])}\n`;
|
||||
res[key].definitions = gql`${res[key].definitions}${fragments[key]}`;
|
||||
res[key].spreads.push(getDefinitionName(fragments[key]));
|
||||
res[key].definitions.push(fragments[key]);
|
||||
});
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
Object.keys(res).forEach(key => {
|
||||
|
||||
// Assemble arguments for `gql` to call it directly without using template literals.
|
||||
res[key].spreads = `...${res[key].spreads.join('\n...')}\n`;
|
||||
res[key].definitions = mergeDocuments(res[key].definitions);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,3 +82,9 @@ export function getSlotsFragments(slots) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getGraphQLExtensions() {
|
||||
return plugins
|
||||
.map(o => pick(o.module, ['mutations', 'queries', 'fragments']))
|
||||
.filter(o => o);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import withFragments from './withFragments';
|
||||
import withReaction from './withReaction';
|
||||
export {default as withFragments} from './withFragments';
|
||||
export {default as withMutation} from './withMutation';
|
||||
export {default as withQuery} from './withQuery';
|
||||
export {default as withReaction} from './withReaction';
|
||||
|
||||
export default {
|
||||
withFragments,
|
||||
withReaction
|
||||
};
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import * as React from 'react';
|
||||
import {graphql} from 'react-apollo';
|
||||
import merge from 'lodash/merge';
|
||||
import uniq from 'lodash/uniq';
|
||||
import flatten from 'lodash/flatten';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import {getMutationOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry';
|
||||
import {store} from 'coral-framework/services/store';
|
||||
import {getDefinitionName} from '../utils';
|
||||
|
||||
/**
|
||||
* Exports a HOC with the same signature as `graphql`, that will
|
||||
* apply mutation options registered in the graphRegistry.
|
||||
*/
|
||||
export default (document, config) => WrappedComponent => {
|
||||
config = {
|
||||
...config,
|
||||
options: config.options || {},
|
||||
props: config.props || (data => ({mutate: data.mutate()})),
|
||||
};
|
||||
const wrappedProps = (data) => {
|
||||
const name = getDefinitionName(document);
|
||||
const callbacks = getMutationOptions(name);
|
||||
const mutate = (base) => {
|
||||
const variables = base.variables || config.options.variables;
|
||||
const configs = callbacks.map(cb => cb({variables, state: store.getState()}));
|
||||
|
||||
const optimisticResponse = merge(
|
||||
base.optimisticResponse || config.options.optimisticResponse,
|
||||
...configs.map(cfg => cfg.optimisticResponse),
|
||||
);
|
||||
|
||||
const refetchQueries = flatten(uniq([
|
||||
base.refetchQueries || config.options.refetchQueries,
|
||||
...configs.map(cfg => cfg.refetchQueries),
|
||||
].filter(i => i)));
|
||||
|
||||
const updateCallbacks =
|
||||
[base.update || config.options.update]
|
||||
.concat(...configs.map(cfg => cfg.update))
|
||||
.filter(i => i);
|
||||
|
||||
const update = (proxy, result) => {
|
||||
updateCallbacks.forEach(cb => cb(proxy, result));
|
||||
};
|
||||
|
||||
const updateQueries =
|
||||
[
|
||||
base.updateQueries || config.options.updateQueries,
|
||||
...configs.map(cfg => cfg.updateQueries)
|
||||
]
|
||||
.filter(i => i)
|
||||
.reduce((res, map) => {
|
||||
Object.keys(map).forEach(key => {
|
||||
if (!(key in res)) {
|
||||
res[key] = map[key];
|
||||
} else {
|
||||
const existing = res[key];
|
||||
res[key] = (prev, result) => {
|
||||
const next = existing(prev, result);
|
||||
return map[key](next, result);
|
||||
};
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
const wrappedConfig = {
|
||||
variables,
|
||||
optimisticResponse,
|
||||
refetchQueries,
|
||||
updateQueries,
|
||||
update,
|
||||
};
|
||||
if (isEmpty(wrappedConfig.optimisticResponse)) {
|
||||
delete wrappedConfig.optimisticResponse;
|
||||
}
|
||||
return data.mutate(wrappedConfig);
|
||||
};
|
||||
return config.props({...data, mutate});
|
||||
};
|
||||
|
||||
// Lazily resolve fragments from graphRegistry to support circular dependencies.
|
||||
let memoized = null;
|
||||
const getWrapped = () => {
|
||||
if (!memoized) {
|
||||
memoized = graphql(resolveFragments(document), {...config, props: wrappedProps})(WrappedComponent);
|
||||
}
|
||||
return memoized;
|
||||
};
|
||||
|
||||
return (props) => {
|
||||
const Wrapped = getWrapped();
|
||||
return <Wrapped {...props} />;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import {graphql} from 'react-apollo';
|
||||
import {getQueryOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry';
|
||||
import {getDefinitionName, separateDataAndRoot} from '../utils';
|
||||
|
||||
/**
|
||||
* Exports a HOC with the same signature as `graphql`, that will
|
||||
* apply query options registered in the graphRegistry.
|
||||
*/
|
||||
export default (document, config) => WrappedComponent => {
|
||||
config = {
|
||||
...config,
|
||||
options: config.options || {},
|
||||
props: config.props || (({data}) => separateDataAndRoot(data)),
|
||||
};
|
||||
|
||||
const wrappedOptions = (data) => {
|
||||
const base = (typeof config.options === 'function') ? config.options(data) : config.options;
|
||||
const name = getDefinitionName(document);
|
||||
const configs = getQueryOptions(name);
|
||||
const reducerCallbacks =
|
||||
[base.reducer || (i => i)]
|
||||
.concat(...configs.map(cfg => cfg.reducer))
|
||||
.filter(i => i);
|
||||
|
||||
const reducer = reducerCallbacks.reduce(
|
||||
(a, b) => (prev, ...rest) =>
|
||||
b(a(prev, ...rest), ...rest),
|
||||
);
|
||||
|
||||
return {
|
||||
...base,
|
||||
reducer,
|
||||
};
|
||||
};
|
||||
|
||||
let memoized = null;
|
||||
const getWrapped = () => {
|
||||
if (!memoized) {
|
||||
memoized = graphql(resolveFragments(document), {...config, options: wrappedOptions})(WrappedComponent);
|
||||
}
|
||||
return memoized;
|
||||
};
|
||||
|
||||
return (props) => {
|
||||
const Wrapped = getWrapped();
|
||||
return <Wrapped {...props} />;
|
||||
};
|
||||
};
|
||||
@@ -1,7 +1,6 @@
|
||||
import pym from './services/PymConnection';
|
||||
import I18n from './modules/i18n/i18n';
|
||||
import actions from './actions';
|
||||
import hocs from './hocs';
|
||||
|
||||
// TODO (bc): Deprecate old actions. Spreading actions is now needed.
|
||||
|
||||
@@ -9,6 +8,5 @@ export default {
|
||||
pym,
|
||||
I18n,
|
||||
actions,
|
||||
...hocs,
|
||||
...actions
|
||||
};
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
|
||||
import {getGraphQLExtensions} from 'coral-framework/helpers/plugins';
|
||||
import globalFragments from 'coral-framework/graphql/fragments';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
const fragments = {};
|
||||
const mutationOptions = {};
|
||||
const queryOptions = {};
|
||||
|
||||
const getTypeName = (ast) => ast.definitions[0].typeCondition.name.value;
|
||||
|
||||
/**
|
||||
* Add fragment
|
||||
*
|
||||
* Example:
|
||||
* addFragment('MyFragment', gql`
|
||||
* fragment Plugin_MyFragment on Comment {
|
||||
* body
|
||||
* }
|
||||
* `);
|
||||
*/
|
||||
export function addFragment(key, document) {
|
||||
const type = getTypeName(document);
|
||||
const name = getDefinitionName(document);
|
||||
if (!(key in fragments)) {
|
||||
fragments[key] = {type, names: [name], documents: [document]};
|
||||
} else {
|
||||
if (type !== fragments[key].type) {
|
||||
console.error(`Type mismatch ${type} !== ${fragments[key].type}`);
|
||||
}
|
||||
fragments[key].names.push(name);
|
||||
fragments[key].documents.push(document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mutation options.
|
||||
*
|
||||
* Example:
|
||||
* // state is the current redux state, which is sometimes
|
||||
* // necessary to fill the optimistic response.
|
||||
* addMutationOptions('PostComment', ({variables, state}) => ({
|
||||
* optimisticResponse: {
|
||||
* CreateComment: {
|
||||
* extra: '',
|
||||
* },
|
||||
* },
|
||||
* refetchQueries: [],
|
||||
* updateQueries: {
|
||||
* EmbedQuery: (previous, data) => {
|
||||
* return previous;
|
||||
* },
|
||||
* },
|
||||
* update: (proxy, result) => {
|
||||
* },
|
||||
* })
|
||||
*/
|
||||
export function addMutationOptions(key, config) {
|
||||
if (!(key in mutationOptions)) {
|
||||
mutationOptions[key] = [config];
|
||||
} else {
|
||||
mutationOptions[key].push(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add query options.
|
||||
*
|
||||
* Example:
|
||||
* addQueryOptions('EmbedQuery', {
|
||||
* reducer: (previousResult, action, variables) => previousResult,
|
||||
* });
|
||||
*/
|
||||
export function addQueryOptions(key, config) {
|
||||
if (!(key in queryOptions)) {
|
||||
queryOptions[key] = [config];
|
||||
} else {
|
||||
queryOptions[key].push(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all fragments, mutation options, and query options defined in the object.
|
||||
*
|
||||
* Example:
|
||||
* add({
|
||||
* fragments: {
|
||||
* CreateCommentResponse: gql`
|
||||
* fragment CoralRandomEmoji_CreateCommentResponse on CreateCommentResponse {
|
||||
* [...]
|
||||
* }`,
|
||||
* },
|
||||
* mutations: {
|
||||
* // state is the current redux state, which is sometimes
|
||||
* // necessary to fill the optimistic response.
|
||||
* PostComment: ({variables, state}) => ({
|
||||
* optimisticResponse: {
|
||||
* [...]
|
||||
* },
|
||||
* refetchQueries: [],
|
||||
* updateQueries: {
|
||||
* EmbedQuery: (previous, data) => {
|
||||
* return previous;
|
||||
* },
|
||||
* },
|
||||
* update: (proxy, result) => {
|
||||
* },
|
||||
* })
|
||||
* },
|
||||
* queries: {
|
||||
* EmbedQuery: {
|
||||
* reducer: (previousResult, action, variables) => {
|
||||
* return previousResult;
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function add(extension) {
|
||||
Object.keys(extension.fragments || []).forEach(key => addFragment(key, extension.fragments[key]));
|
||||
Object.keys(extension.mutations || []).forEach(key => addMutationOptions(key, extension.mutations[key]));
|
||||
Object.keys(extension.queries || []).forEach(key => addQueryOptions(key, extension.queries[key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of mutation options.
|
||||
*/
|
||||
export function getMutationOptions(key) {
|
||||
init();
|
||||
return mutationOptions[key] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of query options.
|
||||
*/
|
||||
export function getQueryOptions(key) {
|
||||
init();
|
||||
return queryOptions[key] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a document with a fragment named `key`, which contains
|
||||
* all fragments added under this key.
|
||||
*/
|
||||
export function getFragmentDocument(key) {
|
||||
init();
|
||||
|
||||
if (!(key in fragments)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let documents = fragments[key] ? fragments[key].documents : [];
|
||||
let fields = fragments[key] ? `...${fragments[key].names.join('\n...')}\n` : ' __typename';
|
||||
|
||||
// Assemble arguments for `gql` to call it directly without using template literals.
|
||||
const main = `
|
||||
fragment ${key} on ${fragments[key].type} {
|
||||
${fields}
|
||||
}
|
||||
`;
|
||||
return mergeDocuments([main, ...documents]);
|
||||
}
|
||||
|
||||
// The fragments and configs are lazily loaded to allow circular dependencies to work.
|
||||
// TODO: We might want to change this to an explicit add after we have lazy Queries and Mutations.
|
||||
let initialized = false;
|
||||
|
||||
function init() {
|
||||
if (initialized) { return; }
|
||||
initialized = true;
|
||||
|
||||
// Add fragments from framework.
|
||||
[globalFragments].forEach(map =>
|
||||
Object.keys(map).forEach(key => addFragment(key, map[key]))
|
||||
);
|
||||
|
||||
// Add configs from plugins.
|
||||
getGraphQLExtensions().forEach(ext => add(ext));
|
||||
}
|
||||
|
||||
export function resolveFragments(document) {
|
||||
if (document.loc.source) {
|
||||
|
||||
// resolve fragments from registry
|
||||
const matchedSubFragments = document.loc.source.body.match(/\.\.\.(.*)/g) || [];
|
||||
const subFragments =
|
||||
uniq(matchedSubFragments.map(f => f.replace('...', '')))
|
||||
.map(key => getFragmentDocument(key))
|
||||
.filter(i => i);
|
||||
|
||||
if (subFragments.length > 0) {
|
||||
return mergeDocuments([document, ...subFragments]);
|
||||
}
|
||||
} else {
|
||||
console.warn('Can only resolve fragments from documents definied using the gql tag.');
|
||||
}
|
||||
return document;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import {gql} from 'react-apollo';
|
||||
|
||||
export const getTotalActionCount = (type, comment) => {
|
||||
return comment.action_summaries
|
||||
.filter(s => s.__typename === type)
|
||||
@@ -61,3 +63,11 @@ export function separateDataAndRoot(
|
||||
root,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeDocuments(documents) {
|
||||
const main = typeof documents[0] === 'string' ? documents[0] : documents[0].loc.source.body;
|
||||
const substitutions = documents.slice(1);
|
||||
const literals = [main, ...substitutions.map(() => '\n')];
|
||||
return gql.apply(null, [literals, ...substitutions]);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class CommentBox extends React.Component {
|
||||
postComment = ({body}) => {
|
||||
const {
|
||||
commentPostedHandler,
|
||||
postItem,
|
||||
postComment,
|
||||
setCommentCountCache,
|
||||
commentCountCache,
|
||||
isReply,
|
||||
@@ -64,7 +64,7 @@ class CommentBox extends React.Component {
|
||||
// Execute preSubmit Hooks
|
||||
this.state.hooks.preSubmit.forEach(hook => hook());
|
||||
|
||||
postItem(comment, 'comments')
|
||||
postComment(comment, 'comments')
|
||||
.then(({data}) => {
|
||||
const postedComment = data.createComment.comment;
|
||||
|
||||
@@ -181,7 +181,7 @@ CommentBox.propTypes = {
|
||||
charCountEnable: PropTypes.bool.isRequired,
|
||||
maxCharCount: PropTypes.number,
|
||||
commentPostedHandler: PropTypes.func,
|
||||
postItem: PropTypes.func.isRequired,
|
||||
postComment: PropTypes.func.isRequired,
|
||||
cancelButtonClicked: PropTypes.func,
|
||||
assetId: PropTypes.string.isRequired,
|
||||
parentId: PropTypes.string,
|
||||
|
||||
@@ -12,7 +12,7 @@ class ReplyBox extends Component {
|
||||
render() {
|
||||
const {
|
||||
styles,
|
||||
postItem,
|
||||
postComment,
|
||||
assetId,
|
||||
authorId,
|
||||
addNotification,
|
||||
@@ -32,7 +32,7 @@ class ReplyBox extends Component {
|
||||
addNotification={addNotification}
|
||||
authorId={authorId}
|
||||
assetId={assetId}
|
||||
postItem={postItem}
|
||||
postComment={postComment}
|
||||
isReply={true} />
|
||||
</div>;
|
||||
}
|
||||
@@ -46,7 +46,7 @@ ReplyBox.propTypes = {
|
||||
parentId: PropTypes.string,
|
||||
addNotification: PropTypes.func.isRequired,
|
||||
authorId: PropTypes.string.isRequired,
|
||||
postItem: PropTypes.func.isRequired,
|
||||
postComment: PropTypes.func.isRequired,
|
||||
assetId: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import {connect} from 'react-redux';
|
||||
import {compose} from 'react-apollo';
|
||||
import {compose, graphql, gql} from 'react-apollo';
|
||||
import React, {Component} from 'react';
|
||||
import I18n from 'coral-framework/modules/i18n/i18n';
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import {myCommentHistory, myIgnoredUsers} from 'coral-framework/graphql/queries';
|
||||
import {stopIgnoringUser} from 'coral-framework/graphql/mutations';
|
||||
import {withStopIgnoringUser} from 'coral-framework/graphql/mutations';
|
||||
|
||||
import {link} from 'coral-framework/services/PymConnection';
|
||||
import NotLoggedIn from '../components/NotLoggedIn';
|
||||
@@ -88,6 +87,38 @@ class ProfileContainer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: These currently relies on refetching (see ignoreUser and stopIgnoringUser mutations).
|
||||
//
|
||||
const withMyIgnoredUsersQuery = graphql(gql`
|
||||
query myIgnoredUsers {
|
||||
myIgnoredUsers {
|
||||
id,
|
||||
username,
|
||||
}
|
||||
}`, {
|
||||
props: ({data}) => {
|
||||
return ({
|
||||
myIgnoredUsersData: data
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const withMyCommentHistoryQuery = graphql(gql`
|
||||
query myCommentHistory {
|
||||
me {
|
||||
comments {
|
||||
id
|
||||
body
|
||||
asset {
|
||||
id
|
||||
title
|
||||
url
|
||||
}
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}`);
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: state.user.toJS(),
|
||||
asset: state.asset.toJS(),
|
||||
@@ -99,7 +130,7 @@ const mapDispatchToProps = dispatch =>
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
myCommentHistory,
|
||||
myIgnoredUsers,
|
||||
stopIgnoringUser,
|
||||
withMyCommentHistoryQuery,
|
||||
withMyIgnoredUsersQuery,
|
||||
withStopIgnoringUser,
|
||||
)(ProfileContainer);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import {compose, gql, graphql} from 'react-apollo';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import get from 'lodash/get';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {withFragments, withMutation} from 'coral-framework/hocs';
|
||||
import {showSignInDialog} from 'coral-framework/actions/auth';
|
||||
import RespectButton from '../components/RespectButton';
|
||||
|
||||
const isRespectAction = (a) => a.__typename === 'RespectActionSummary';
|
||||
|
||||
const COMMENT_FRAGMENT = gql`
|
||||
fragment RespectButton_updateFragment on Comment {
|
||||
fragment CoralRespect_UpdateFragment on Comment {
|
||||
action_summaries {
|
||||
... on RespectActionSummary {
|
||||
count
|
||||
@@ -21,8 +21,8 @@ const COMMENT_FRAGMENT = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const withDeleteAction = graphql(gql`
|
||||
mutation deleteAction($id: ID!) {
|
||||
const withDeleteAction = withMutation(gql`
|
||||
mutation CoralRespect_DeleteAction($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
@@ -66,8 +66,8 @@ const withDeleteAction = graphql(gql`
|
||||
}),
|
||||
});
|
||||
|
||||
const withPostRespect = graphql(gql`
|
||||
mutation createRespect($respect: CreateRespectInput!) {
|
||||
const withPostRespect = withMutation(gql`
|
||||
mutation CoralRespect_CreateRespect($respect: CreateRespectInput!) {
|
||||
createRespect(respect: $respect) {
|
||||
respect {
|
||||
id
|
||||
@@ -137,14 +137,14 @@ const mapDispatchToProps = dispatch =>
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment RespectButton_root on RootQuery {
|
||||
fragment CoralRespect_RespectButton_root on RootQuery {
|
||||
me {
|
||||
status
|
||||
}
|
||||
}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment RespectButton_comment on Comment {
|
||||
fragment CoralRespect_RespectButton_comment on Comment {
|
||||
action_summaries {
|
||||
... on RespectActionSummary {
|
||||
count
|
||||
|
||||
Reference in New Issue
Block a user