replaced eslint:recommended with prettier

This commit is contained in:
Wyatt Johnson
2018-01-11 20:00:34 -07:00
parent d56c19016a
commit 0abc2ca243
649 changed files with 16235 additions and 13008 deletions
+5 -2
View File
@@ -1,2 +1,5 @@
export {addTag, removeTag} from 'talk-plugin-commentbox/actions';
export {addCommentClassName, removeCommentClassName} from 'coral-embed-stream/src/actions/stream';
export { addTag, removeTag } from 'talk-plugin-commentbox/actions';
export {
addCommentClassName,
removeCommentClassName,
} from 'coral-embed-stream/src/actions/stream';
+3 -2
View File
@@ -1,2 +1,3 @@
export const commentBoxTagsSelector = (state) => state.commentBox.tags;
export const commentClassNamesSelector = (state) => state.stream.commentClassNames;
export const commentBoxTagsSelector = state => state.commentBox.tags;
export const commentClassNamesSelector = state =>
state.stream.commentClassNames;
+1 -1
View File
@@ -1 +1 @@
export {viewUserDetail} from 'coral-admin/src/actions/userDetail';
export { viewUserDetail } from 'coral-admin/src/actions/userDetail';
@@ -1 +1 @@
export {notify} from 'coral-framework/actions/notification';
export { notify } from 'coral-framework/actions/notification';
+1 -1
View File
@@ -1 +1 @@
export {setSort} from 'coral-embed-stream/src/actions/stream';
export { setSort } from 'coral-embed-stream/src/actions/stream';
@@ -19,7 +19,6 @@ export default class SortOption extends React.Component {
}
SortOption.propTypes = {
// A simple callback to be called when clicking on this sort option.
setSort: PropTypes.func.isRequired,
+28 -10
View File
@@ -1,10 +1,28 @@
export {Slot} from 'coral-framework/components';
export {default as ClickOutside} from 'coral-framework/components/ClickOutside';
export {default as IfSlotIsEmpty} from 'coral-framework/components/IfSlotIsEmpty';
export {default as IfSlotIsNotEmpty} from 'coral-framework/components/IfSlotIsNotEmpty';
export {default as CommentAuthorName} from 'coral-framework/components/CommentAuthorName';
export {default as CommentTimestamp} from 'coral-framework/components/CommentTimestamp';
export {default as CommentDetail} from 'coral-framework/components/CommentDetail';
export {default as CommentContent} from 'coral-framework/components/CommentContent';
export {default as ConfigureCard} from 'coral-framework/components/ConfigureCard';
export {default as StreamConfiguration} from 'coral-framework/components/StreamConfiguration';
export { Slot } from 'coral-framework/components';
export {
default as ClickOutside,
} from 'coral-framework/components/ClickOutside';
export {
default as IfSlotIsEmpty,
} from 'coral-framework/components/IfSlotIsEmpty';
export {
default as IfSlotIsNotEmpty,
} from 'coral-framework/components/IfSlotIsNotEmpty';
export {
default as CommentAuthorName,
} from 'coral-framework/components/CommentAuthorName';
export {
default as CommentTimestamp,
} from 'coral-framework/components/CommentTimestamp';
export {
default as CommentDetail,
} from 'coral-framework/components/CommentDetail';
export {
default as CommentContent,
} from 'coral-framework/components/CommentContent';
export {
default as ConfigureCard,
} from 'coral-framework/components/ConfigureCard';
export {
default as StreamConfiguration,
} from 'coral-framework/components/StreamConfiguration';
+2 -1
View File
@@ -9,4 +9,5 @@ import SortOption from '../components/SortOption';
* @param {string} sort.sortOrder
* @return {Object} Component
*/
export const createSortOption = (label, sort) => withSortOption({...sort, label})(SortOption);
export const createSortOption = (label, sort) =>
withSortOption({ ...sort, label })(SortOption);
+7 -7
View File
@@ -1,10 +1,10 @@
export {default as withReaction} from './withReaction';
export {default as withTags} from './withTags';
export {default as withSortOption} from './withSortOption';
export {default as withFragments} from 'coral-framework/hocs/withFragments';
export {default as excludeIf} from 'coral-framework/hocs/excludeIf';
export {default as connect} from 'coral-framework/hocs/connect';
export {default as withEmit} from 'coral-framework/hocs/withEmit';
export { default as withReaction } from './withReaction';
export { default as withTags } from './withTags';
export { default as withSortOption } from './withSortOption';
export { default as withFragments } from 'coral-framework/hocs/withFragments';
export { default as excludeIf } from 'coral-framework/hocs/excludeIf';
export { default as connect } from 'coral-framework/hocs/connect';
export { default as withEmit } from 'coral-framework/hocs/withEmit';
export {
withIgnoreUser,
withBanUser,
+359 -328
View File
@@ -1,22 +1,26 @@
import React from 'react';
import get from 'lodash/get';
import uuid from 'uuid/v4';
import {connect} from 'plugin-api/beta/client/hocs';
import {bindActionCreators} from 'redux';
import {getDisplayName} from 'coral-framework/helpers/hoc';
import {compose, gql} from 'react-apollo';
import { connect } from 'plugin-api/beta/client/hocs';
import { bindActionCreators } from 'redux';
import { getDisplayName } from 'coral-framework/helpers/hoc';
import { compose, gql } from 'react-apollo';
import withFragments from 'coral-framework/hocs/withFragments';
import withMutation from 'coral-framework/hocs/withMutation';
import {notify} from 'coral-framework/actions/notification';
import {capitalize} from 'coral-framework/helpers/strings';
import {getMyActionSummary, getTotalActionCount, getErrorMessages} from 'coral-framework/utils';
import { notify } from 'coral-framework/actions/notification';
import { capitalize } from 'coral-framework/helpers/strings';
import {
getMyActionSummary,
getTotalActionCount,
getErrorMessages,
} from 'coral-framework/utils';
import hoistStatics from 'recompose/hoistStatics';
import * as PropTypes from 'prop-types';
import {getDefinitionName} from '../utils';
import {t, can} from 'plugin-api/beta/client/services';
import { getDefinitionName } from '../utils';
import { t, can } from 'plugin-api/beta/client/services';
// TODO: Auth logic needs refactoring.
import {showSignInDialog} from 'coral-embed-stream/src/actions/auth';
import { showSignInDialog } from 'coral-embed-stream/src/actions/auth';
/*
* Disable false-positive warning below, as it doesn't work well with how we currently
@@ -28,26 +32,27 @@ import {showSignInDialog} from 'coral-embed-stream/src/actions/auth';
*/
gql.disableFragmentWarnings();
export default (reaction, options = {}) => hoistStatics((WrappedComponent) => {
if (typeof reaction !== 'string') {
console.error('Reaction must be a valid string');
return null;
}
export default (reaction, options = {}) =>
hoistStatics(WrappedComponent => {
if (typeof reaction !== 'string') {
console.error('Reaction must be a valid string');
return null;
}
// fragments allow the extension of the fragments defined in this HOC.
const {fragments = {}} = options;
// fragments allow the extension of the fragments defined in this HOC.
const { fragments = {} } = options;
// Global instance counter for each `reaction` type.
let instances = 0;
// Global instance counter for each `reaction` type.
let instances = 0;
// Track current subscriptions.
let createdSubscription = null;
let deletedSubscription = null;
// Track current subscriptions.
let createdSubscription = null;
let deletedSubscription = null;
reaction = reaction.toLowerCase();
const Reaction = capitalize(reaction);
reaction = reaction.toLowerCase();
const Reaction = capitalize(reaction);
const COMMENT_FRAGMENT = gql`
const COMMENT_FRAGMENT = gql`
fragment ${Reaction}Button_updateFragment on Comment {
action_summaries {
... on ${Reaction}ActionSummary {
@@ -60,99 +65,98 @@ export default (reaction, options = {}) => hoistStatics((WrappedComponent) => {
}
`;
const isReaction = (a) =>
a.__typename === `${Reaction}ActionSummary`;
const isReaction = a => a.__typename === `${Reaction}ActionSummary`;
const addReactionToStore = (proxy, {action, self}) => {
const fragmentId = `Comment_${action.item_id}`;
const addReactionToStore = (proxy, { action, self }) => {
const fragmentId = `Comment_${action.item_id}`;
// Read the data from our cache for this query.
const data = proxy.readFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId
});
if (!data) {
if (self) {
throw new Error(`Comment ${action.item_id} was not found`);
}
return;
}
// Add our comment from the mutation to the end.
let idx = data.action_summaries.findIndex(isReaction);
// Check whether we already reactioned this comment.
if (self && idx >= 0 && data.action_summaries[idx].current_user) {
return;
}
if (idx < 0) {
// Add initial action when it doesn't exist.
data.action_summaries.push({
__typename: `${Reaction}ActionSummary`,
count: 0,
current_user: null
// Read the data from our cache for this query.
const data = proxy.readFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
});
idx = data.action_summaries.length - 1;
}
data.action_summaries[idx] = {
...data.action_summaries[idx],
count: data.action_summaries[idx].count + 1,
current_user: self ? action : data.action_summaries[idx].current_user
};
// Write our data back to the cache.
proxy.writeFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
data
});
};
const deleteReactionFromStore = (proxy, {action, self}) => {
const fragmentId = `Comment_${action.item_id}`;
// Read the data from our cache for this query.
const data = proxy.readFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId
});
if (!data) {
if (self) {
throw new Error(`Comment ${action.item_id} was not found`);
if (!data) {
if (self) {
throw new Error(`Comment ${action.item_id} was not found`);
}
return;
}
return;
}
// Check whether we liked this comment.
const idx = data.action_summaries.findIndex(isReaction);
// Add our comment from the mutation to the end.
let idx = data.action_summaries.findIndex(isReaction);
if (
self &&
(idx < 0 || get(data.action_summaries[idx], 'current_user.id') !== action.id)
) {
return;
}
// Check whether we already reactioned this comment.
if (self && idx >= 0 && data.action_summaries[idx].current_user) {
return;
}
data.action_summaries[idx] = {
...data.action_summaries[idx],
count: data.action_summaries[idx].count - 1,
current_user: self ? null : data.action_summaries[idx].current_user,
if (idx < 0) {
// Add initial action when it doesn't exist.
data.action_summaries.push({
__typename: `${Reaction}ActionSummary`,
count: 0,
current_user: null,
});
idx = data.action_summaries.length - 1;
}
data.action_summaries[idx] = {
...data.action_summaries[idx],
count: data.action_summaries[idx].count + 1,
current_user: self ? action : data.action_summaries[idx].current_user,
};
// Write our data back to the cache.
proxy.writeFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
data,
});
};
// Write our data back to the cache.
proxy.writeFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
data
});
};
const deleteReactionFromStore = (proxy, { action, self }) => {
const fragmentId = `Comment_${action.item_id}`;
const REACTION_CREATED_SUBSCRIPTION = gql`
// Read the data from our cache for this query.
const data = proxy.readFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
});
if (!data) {
if (self) {
throw new Error(`Comment ${action.item_id} was not found`);
}
return;
}
// Check whether we liked this comment.
const idx = data.action_summaries.findIndex(isReaction);
if (
self &&
(idx < 0 ||
get(data.action_summaries[idx], 'current_user.id') !== action.id)
) {
return;
}
data.action_summaries[idx] = {
...data.action_summaries[idx],
count: data.action_summaries[idx].count - 1,
current_user: self ? null : data.action_summaries[idx].current_user,
};
// Write our data back to the cache.
proxy.writeFragment({
fragment: COMMENT_FRAGMENT,
id: fragmentId,
data,
});
};
const REACTION_CREATED_SUBSCRIPTION = gql`
subscription ${Reaction}ActionCreated($assetId: ID!) {
${reaction}ActionCreated(asset_id: $assetId) {
id
@@ -164,7 +168,7 @@ export default (reaction, options = {}) => hoistStatics((WrappedComponent) => {
}
`;
const REACTION_DELETED_SUBSCRIPTION = gql`
const REACTION_DELETED_SUBSCRIPTION = gql`
subscription ${Reaction}ActionDeleted($assetId: ID!) {
${reaction}ActionDeleted(asset_id: $assetId) {
id
@@ -176,156 +180,171 @@ export default (reaction, options = {}) => hoistStatics((WrappedComponent) => {
}
`;
class WithReactions extends React.Component {
class WithReactions extends React.Component {
static contextTypes = {
client: PropTypes.object.isRequired,
};
static contextTypes = {
client: PropTypes.object.isRequired,
};
// Whether or not a mutation is currently active.
duringMutation = false;
// Whether or not a mutation is currently active.
duringMutation = false;
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
// Start subscriptions when it is first needed.
if (instances === 0) {
createdSubscription = context.client
.subscribe({
query: REACTION_CREATED_SUBSCRIPTION,
variables: {
assetId: this.props.asset.id,
},
})
.subscribe({
next: this.onReactionCreated,
error(err) {
console.error('err', err);
},
});
// Start subscriptions when it is first needed.
if (instances === 0) {
createdSubscription = context.client.subscribe({
query: REACTION_CREATED_SUBSCRIPTION,
variables: {
assetId: this.props.asset.id,
},
}).subscribe({
next: this.onReactionCreated,
error(err) { console.error('err', err); },
});
deletedSubscription = context.client.subscribe({
query: REACTION_DELETED_SUBSCRIPTION,
variables: {
assetId: this.props.asset.id,
},
}).subscribe({
next: this.onReactionDeleted,
error(err) { console.error('err', err); },
});
}
instances++;
}
// onReactionCreated handles live updates through the subscriptions.
onReactionCreated = ({[`${reaction}ActionCreated`]: action}) => {
if (this.props.user && action.user && this.props.user.id === action.user.id) {
return;
}
addReactionToStore(this.context.client, {action, self: false});
};
// onReactionDeleted handles live updates through the subscriptions.
onReactionDeleted = ({[`${reaction}ActionDeleted`]: action}) => {
if (this.props.user && action.user && this.props.user.id === action.user.id) {
return;
}
deleteReactionFromStore(this.context.client, {action, self: false});
};
componentWillUnmount() {
instances--;
// End subscriptions when last component will be unmounted.
if (instances === 0) {
try {
createdSubscription.unsubscribe();
deletedSubscription.unsubscribe();
deletedSubscription = context.client
.subscribe({
query: REACTION_DELETED_SUBSCRIPTION,
variables: {
assetId: this.props.asset.id,
},
})
.subscribe({
next: this.onReactionDeleted,
error(err) {
console.error('err', err);
},
});
}
catch(e) {
console.warn(e);
instances++;
}
// onReactionCreated handles live updates through the subscriptions.
onReactionCreated = ({ [`${reaction}ActionCreated`]: action }) => {
if (
this.props.user &&
action.user &&
this.props.user.id === action.user.id
) {
return;
}
addReactionToStore(this.context.client, { action, self: false });
};
// onReactionDeleted handles live updates through the subscriptions.
onReactionDeleted = ({ [`${reaction}ActionDeleted`]: action }) => {
if (
this.props.user &&
action.user &&
this.props.user.id === action.user.id
) {
return;
}
deleteReactionFromStore(this.context.client, { action, self: false });
};
componentWillUnmount() {
instances--;
// End subscriptions when last component will be unmounted.
if (instances === 0) {
try {
createdSubscription.unsubscribe();
deletedSubscription.unsubscribe();
} catch (e) {
console.warn(e);
}
}
}
postReaction = () => {
if (this.duringMutation) {
return;
}
this.duringMutation = true;
// If the current user is suspended, do nothing.
if (!can(this.props.user, 'INTERACT_WITH_COMMUNITY')) {
notify('error', t('error.NOT_AUTHORIZED'));
return;
}
return this.props
.postReaction(this.props.comment)
.then(result => {
this.duringMutation = false;
return result;
})
.catch(err => {
this.duringMutation = false;
this.props.notify('error', getErrorMessages(err));
throw err;
});
};
deleteReaction = () => {
if (this.duringMutation) {
return;
}
this.duringMutation = true;
// If the current user is suspended, do nothing.
if (!can(this.props.user, 'INTERACT_WITH_COMMUNITY')) {
notify('error', t('error.NOT_AUTHORIZED'));
return;
}
return this.props
.deleteReaction(this.props.comment)
.then(result => {
this.duringMutation = false;
return result;
})
.catch(err => {
this.duringMutation = false;
this.props.notify('error', getErrorMessages(err));
throw err;
});
};
render() {
const { root, asset, comment } = this.props;
const reactionSummary = getMyActionSummary(
`${Reaction}ActionSummary`,
comment
);
const count = getTotalActionCount(`${Reaction}ActionSummary`, comment);
const alreadyReacted = !!reactionSummary;
return (
<WrappedComponent
root={root}
asset={asset}
comment={comment}
showSignInDialog={this.props.showSignInDialog}
notify={this.props.notify}
user={this.props.user}
reactionSummary={reactionSummary}
count={count}
alreadyReacted={alreadyReacted}
postReaction={this.postReaction}
deleteReaction={this.deleteReaction}
config={this.props.config}
/>
);
}
}
postReaction = () => {
if (this.duringMutation) {
return;
}
this.duringMutation = true;
// If the current user is suspended, do nothing.
if (!can(this.props.user, 'INTERACT_WITH_COMMUNITY')) {
notify('error', t('error.NOT_AUTHORIZED'));
return;
}
return this.props.postReaction(this.props.comment)
.then((result) => {
this.duringMutation = false;
return result;
})
.catch((err) => {
this.duringMutation = false;
this.props.notify('error', getErrorMessages(err));
throw err;
});
}
deleteReaction = () => {
if (this.duringMutation) {
return;
}
this.duringMutation = true;
// If the current user is suspended, do nothing.
if (!can(this.props.user, 'INTERACT_WITH_COMMUNITY')) {
notify('error', t('error.NOT_AUTHORIZED'));
return;
}
return this.props.deleteReaction(this.props.comment)
.then((result) => {
this.duringMutation = false;
return result;
})
.catch((err) => {
this.duringMutation = false;
this.props.notify('error', getErrorMessages(err));
throw err;
});
}
render() {
const {root, asset, comment} = this.props;
const reactionSummary = getMyActionSummary(
`${Reaction}ActionSummary`,
comment
);
const count = getTotalActionCount(
`${Reaction}ActionSummary`,
comment
);
const alreadyReacted = !!reactionSummary;
return <WrappedComponent
root={root}
asset={asset}
comment={comment}
showSignInDialog={this.props.showSignInDialog}
notify={this.props.notify}
user={this.props.user}
reactionSummary={reactionSummary}
count={count}
alreadyReacted={alreadyReacted}
postReaction={this.postReaction}
deleteReaction={this.deleteReaction}
config={this.props.config}
/>;
}
}
const withDeleteReaction = withMutation(
gql`
const withDeleteReaction = withMutation(
gql`
mutation Delete${Reaction}Action($input: Delete${Reaction}ActionInput!) {
delete${Reaction}Action(input: $input) {
errors {
@@ -334,38 +353,40 @@ export default (reaction, options = {}) => hoistStatics((WrappedComponent) => {
}
}
`,
{
props: ({mutate}) => ({
deleteReaction: (comment) => {
{
props: ({ mutate }) => ({
deleteReaction: comment => {
const reactionSummary = getMyActionSummary(
`${Reaction}ActionSummary`,
comment
);
const reactionSummary = getMyActionSummary(
`${Reaction}ActionSummary`,
comment
);
const id = reactionSummary.current_user.id;
const item_id = comment.id;
const id = reactionSummary.current_user.id;
const item_id = comment.id;
const input = { id };
return mutate({
variables: { input },
optimisticResponse: {
[`delete${Reaction}Action`]: {
__typename: `Delete${Reaction}ActionResponse`,
errors: null,
},
},
update: proxy => {
deleteReactionFromStore(proxy, {
action: { item_id, id },
self: true,
});
},
});
},
}),
}
);
const input = {id};
return mutate({
variables: {input},
optimisticResponse: {
[`delete${Reaction}Action`]: {
__typename: `Delete${Reaction}ActionResponse`,
errors: null
}
},
update: (proxy) => {
deleteReactionFromStore(proxy, {action: {item_id, id}, self: true});
}
});
}
})
}
);
const withPostReaction = withMutation(
gql`
const withPostReaction = withMutation(
gql`
mutation Create${Reaction}Action($input: Create${Reaction}ActionInput!) {
create${Reaction}Action(input: $input) {
${reaction} {
@@ -377,57 +398,61 @@ export default (reaction, options = {}) => hoistStatics((WrappedComponent) => {
}
}
`,
{
props: ({mutate}) => ({
postReaction: (comment) => {
{
props: ({ mutate }) => ({
postReaction: comment => {
const input = {
item_id: comment.id,
};
const input = {
item_id: comment.id,
};
return mutate({
variables: {input},
optimisticResponse: {
[`create${Reaction}Action`]: {
__typename: `Create${Reaction}ActionResponse`,
errors: null,
[reaction]: {
__typename: `${Reaction}Action`,
id: uuid()
return mutate({
variables: { input },
optimisticResponse: {
[`create${Reaction}Action`]: {
__typename: `Create${Reaction}ActionResponse`,
errors: null,
[reaction]: {
__typename: `${Reaction}Action`,
id: uuid(),
},
},
},
update: (
proxy,
{
data: { [`create${Reaction}Action`]: { [reaction]: action } },
}
}
},
update: (proxy, {data: {[`create${Reaction}Action`]: {[reaction]: action}}}) => {
const a = {
...action,
item_id: input.item_id,
};
addReactionToStore(proxy, {action: a, self: true});
}
});
}
})
}
);
) => {
const a = {
...action,
item_id: input.item_id,
};
addReactionToStore(proxy, { action: a, self: true });
},
});
},
}),
}
);
const mapStateToProps = (state) => ({
user: state.auth.user,
});
const mapStateToProps = state => ({
user: state.auth.user,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({showSignInDialog, notify}, dispatch);
const mapDispatchToProps = dispatch =>
bindActionCreators({ showSignInDialog, notify }, dispatch);
const enhance = compose(
withFragments({
...fragments,
asset: gql`
const enhance = compose(
withFragments({
...fragments,
asset: gql`
fragment ${Reaction}Button_asset on Asset {
id
${fragments.asset ? `...${getDefinitionName(fragments.asset)}` : ''}
}
${fragments.asset ? fragments.asset : ''}
`,
comment: gql`
comment: gql`
fragment ${Reaction}Button_comment on Comment {
id
action_summaries {
@@ -439,17 +464,23 @@ export default (reaction, options = {}) => hoistStatics((WrappedComponent) => {
}
}
}
${fragments.comment ? `...${getDefinitionName(fragments.comment)}` : ''}
${
fragments.comment
? `...${getDefinitionName(fragments.comment)}`
: ''
}
}
${fragments.comment ? fragments.comment : ''}
`
}),
connect(mapStateToProps, mapDispatchToProps),
withDeleteReaction,
withPostReaction
);
`,
}),
connect(mapStateToProps, mapDispatchToProps),
withDeleteReaction,
withPostReaction
);
WithReactions.displayName = `WithReactions(${getDisplayName(WrappedComponent)})`;
WithReactions.displayName = `WithReactions(${getDisplayName(
WrappedComponent
)})`;
return enhance(WithReactions);
});
return enhance(WithReactions);
});
+31 -26
View File
@@ -1,17 +1,20 @@
import React from 'react';
import {connect} from 'plugin-api/beta/client/hocs';
import {bindActionCreators} from 'redux';
import {sortOrderSelector, sortBySelector} from 'plugin-api/beta/client/selectors/stream';
import {setSort} from 'plugin-api/beta/client/actions/stream';
import { connect } from 'plugin-api/beta/client/hocs';
import { bindActionCreators } from 'redux';
import {
sortOrderSelector,
sortBySelector,
} from 'plugin-api/beta/client/selectors/stream';
import { setSort } from 'plugin-api/beta/client/actions/stream';
import hoistStatics from 'recompose/hoistStatics';
import {closeMenu} from 'plugins/talk-plugin-viewing-options/client/actions';
import { closeMenu } from 'plugins/talk-plugin-viewing-options/client/actions';
const mapStateToProps = (state) => ({
const mapStateToProps = state => ({
sortOrder: sortOrderSelector(state),
sortBy: sortBySelector(state),
});
const mapDispatchToProps = (dispatch) =>
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
setSort,
@@ -28,24 +31,26 @@ const mapDispatchToProps = (dispatch) =>
* @param {string} sort.sortOrder
* @return {Object} HOC
*/
export default ({sortBy = 'created_at', sortOrder = 'DESC', label}) => hoistStatics((WrappedComponent) => {
class WithSortOption extends React.Component {
setSort = () => {
this.props.closeMenu();
this.props.setSort({sortBy, sortOrder});
}
export default ({ sortBy = 'created_at', sortOrder = 'DESC', label }) =>
hoistStatics(WrappedComponent => {
class WithSortOption extends React.Component {
setSort = () => {
this.props.closeMenu();
this.props.setSort({ sortBy, sortOrder });
};
render() {
const active = this.props.sortOrder === sortOrder && this.props.sortBy === sortBy;
const resolvedLabel = typeof label === 'function' ? label() : label;
return (
<WrappedComponent
active={active}
setSort={this.setSort}
label={resolvedLabel}
/>
);
render() {
const active =
this.props.sortOrder === sortOrder && this.props.sortBy === sortBy;
const resolvedLabel = typeof label === 'function' ? label() : label;
return (
<WrappedComponent
active={active}
setSort={this.setSort}
label={resolvedLabel}
/>
);
}
}
}
return connect(mapStateToProps, mapDispatchToProps)(WithSortOption);
});
return connect(mapStateToProps, mapDispatchToProps)(WithSortOption);
});
+114 -105
View File
@@ -1,15 +1,15 @@
import React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
import {getDisplayName} from 'coral-framework/helpers/hoc';
import {capitalize} from 'coral-framework/helpers/strings';
import {withAddTag, withRemoveTag} from 'coral-framework/graphql/mutations';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { compose, gql } from 'react-apollo';
import { getDisplayName } from 'coral-framework/helpers/hoc';
import { capitalize } from 'coral-framework/helpers/strings';
import { withAddTag, withRemoveTag } from 'coral-framework/graphql/mutations';
import withFragments from 'coral-framework/hocs/withFragments';
import {notify} from 'coral-framework/actions/notification';
import {getErrorMessages, isTagged} from 'coral-framework/utils';
import { notify } from 'coral-framework/actions/notification';
import { getErrorMessages, isTagged } from 'coral-framework/utils';
import hoistStatics from 'recompose/hoistStatics';
import {getDefinitionName} from '../utils';
import { getDefinitionName } from '../utils';
/*
* Disable false-positive warning below, as it doesn't work well with how we currently
@@ -21,108 +21,113 @@ import {getDefinitionName} from '../utils';
*/
gql.disableFragmentWarnings();
export default (tag, options = {}) => hoistStatics((WrappedComponent) => {
if (typeof tag !== 'string') {
console.error('Tag must be a valid string');
return null;
}
export default (tag, options = {}) =>
hoistStatics(WrappedComponent => {
if (typeof tag !== 'string') {
console.error('Tag must be a valid string');
return null;
}
// fragments allow the extension of the fragments defined in this HOC.
const {fragments = {}} = options;
// fragments allow the extension of the fragments defined in this HOC.
const { fragments = {} } = options;
const Tag = capitalize(tag);
const TAG = tag.toUpperCase();
const Tag = capitalize(tag);
const TAG = tag.toUpperCase();
class WithTags extends React.Component {
loading = false;
class WithTags extends React.Component {
loading = false;
postTag = () => {
const {comment, asset, notify} = this.props;
postTag = () => {
const { comment, asset, notify } = this.props;
if (this.loading) {
return;
if (this.loading) {
return;
}
this.loading = true;
return this.props
.addTag({
id: comment.id,
name: TAG,
assetId: asset.id,
itemType: 'COMMENTS',
})
.then(result => {
this.loading = false;
return result;
})
.catch(err => {
this.loading = false;
notify('error', getErrorMessages(err));
throw err;
});
};
deleteTag = () => {
const { comment, asset, notify } = this.props;
if (this.loading) {
return;
}
this.props
.removeTag({
id: comment.id,
name: TAG,
assetId: asset.id,
itemType: 'COMMENTS',
})
.then(result => {
this.loading = false;
return result;
})
.catch(err => {
this.loading = false;
notify('error', getErrorMessages(err));
throw err;
});
};
render() {
const { root, asset, comment, user, config, ...rest } = this.props;
const alreadyTagged = isTagged(comment.tags, TAG);
return (
<WrappedComponent
{...rest}
root={root}
asset={asset}
comment={comment}
user={user}
alreadyTagged={alreadyTagged}
postTag={this.postTag}
deleteTag={this.deleteTag}
config={config}
/>
);
}
this.loading = true;
return this.props.addTag({
id: comment.id,
name: TAG,
assetId: asset.id,
itemType: 'COMMENTS',
})
.then((result) => {
this.loading = false;
return result;
})
.catch((err) => {
this.loading = false;
notify('error', getErrorMessages(err));
throw err;
});
}
deleteTag = () => {
const {comment, asset, notify} = this.props;
const mapStateToProps = state => ({
user: state.auth.user,
});
if (this.loading) {
return;
}
const mapDispatchToProps = dispatch =>
bindActionCreators({ notify }, dispatch);
this.props.removeTag({
id: comment.id,
name: TAG,
assetId: asset.id,
itemType: 'COMMENTS',
})
.then((result) => {
this.loading = false;
return result;
})
.catch((err) => {
this.loading = false;
notify('error', getErrorMessages(err));
throw err;
});
}
render() {
const {root, asset, comment, user, config, ...rest} = this.props;
const alreadyTagged = isTagged(comment.tags, TAG);
return <WrappedComponent
{...rest}
root={root}
asset={asset}
comment={comment}
user={user}
alreadyTagged={alreadyTagged}
postTag={this.postTag}
deleteTag={this.deleteTag}
config={config}
/>;
}
}
const mapStateToProps = (state) => ({
user: state.auth.user,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({notify}, dispatch);
const enhance = compose(
withFragments({
...fragments,
asset: gql`
const enhance = compose(
withFragments({
...fragments,
asset: gql`
fragment ${Tag}Button_asset on Asset {
id
${fragments.asset ? `...${getDefinitionName(fragments.asset)}` : ''}
}
${fragments.asset ? fragments.asset : ''}
`,
comment: gql`
comment: gql`
fragment ${Tag}Button_comment on Comment {
id
tags {
@@ -130,17 +135,21 @@ export default (tag, options = {}) => hoistStatics((WrappedComponent) => {
name
}
}
${fragments.comment ? `...${getDefinitionName(fragments.comment)}` : ''}
${
fragments.comment
? `...${getDefinitionName(fragments.comment)}`
: ''
}
}
${fragments.comment ? fragments.comment : ''}
`
}),
withAddTag,
withRemoveTag,
connect(mapStateToProps, mapDispatchToProps),
);
`,
}),
withAddTag,
withRemoveTag,
connect(mapStateToProps, mapDispatchToProps)
);
WithTags.displayName = `WithTags(${getDisplayName(WrappedComponent)})`;
WithTags.displayName = `WithTags(${getDisplayName(WrappedComponent)})`;
return enhance(WithTags);
});
return enhance(WithTags);
});
+1 -1
View File
@@ -1 +1 @@
export const pluginConfigSelector = (state) => state.config.pluginConfig;
export const pluginConfigSelector = state => state.config.pluginConfig;
+2 -2
View File
@@ -1,2 +1,2 @@
export const sortOrderSelector = (state) => state.stream.sortOrder;
export const sortBySelector = (state) => state.stream.sortBy;
export const sortOrderSelector = state => state.stream.sortOrder;
export const sortBySelector = state => state.stream.sortBy;
+2 -3
View File
@@ -1,3 +1,2 @@
export {t, timeago} from 'coral-framework/services/i18n';
export {can} from 'coral-framework/services/perms';
export { t, timeago } from 'coral-framework/services/i18n';
export { can } from 'coral-framework/services/perms';
+70 -48
View File
@@ -1,22 +1,24 @@
const {SEARCH_OTHER_USERS} = require('../../../perms/constants');
const { SEARCH_OTHER_USERS } = require('../../../perms/constants');
const errors = require('../../../errors');
const pluralize = require('pluralize');
const sc = require('snake-case');
const CommentModel = require('../../../models/comment');
const {CREATE_MONGO_INDEXES} = require('../../../config');
const { CREATE_MONGO_INDEXES } = require('../../../config');
function getReactionConfig(reaction) {
reaction = reaction.toLowerCase();
if (CREATE_MONGO_INDEXES) {
// Create the index on the comment model based on the reaction config.
CommentModel.collection.createIndex({
created_at: 1,
[`action_counts.${sc(reaction)}`]: 1
}, {
background: true,
});
CommentModel.collection.createIndex(
{
created_at: 1,
[`action_counts.${sc(reaction)}`]: 1,
},
{
background: true,
}
);
}
const reactionPlural = pluralize(reaction);
@@ -126,32 +128,39 @@ function getReactionConfig(reaction) {
return {
typeDefs,
schemas: ({CommentSchema}) => {
CommentSchema.index({
'created_at': 1,
[`action_counts.${sc(reaction)}`]: 1,
}, {
background: true,
});
schemas: ({ CommentSchema }) => {
CommentSchema.index(
{
created_at: 1,
[`action_counts.${sc(reaction)}`]: 1,
},
{
background: true,
}
);
},
context: {
Sort: () => ({
Comments: {
[reactionPlural]: {
startCursor(ctx, nodes, {cursor}) {
startCursor(ctx, nodes, { cursor }) {
// The cursor is the start! This is using numeric pagination.
return cursor != null ? cursor : 0;
},
endCursor(ctx, nodes, {cursor}) {
return nodes.length ? (cursor != null ? cursor : 0) + nodes.length : null;
endCursor(ctx, nodes, { cursor }) {
return nodes.length
? (cursor != null ? cursor : 0) + nodes.length
: null;
},
sort(ctx, query, {cursor, sortOrder}) {
sort(ctx, query, { cursor, sortOrder }) {
if (cursor) {
query = query.skip(cursor);
}
return query.sort({[`action_counts.${reaction}`]: sortOrder === 'DESC' ? -1 : 1, created_at: sortOrder === 'DESC' ? -1 : 1});
return query.sort({
[`action_counts.${reaction}`]: sortOrder === 'DESC' ? -1 : 1,
created_at: sortOrder === 'DESC' ? -1 : 1,
});
},
},
},
@@ -159,37 +168,43 @@ function getReactionConfig(reaction) {
},
resolvers: {
Subscription: {
[`${reaction}ActionCreated`]: ({action}) => {
[`${reaction}ActionCreated`]: ({ action }) => {
return action;
},
[`${reaction}ActionDeleted`]: ({action}) => {
[`${reaction}ActionDeleted`]: ({ action }) => {
return action;
},
},
[`${Reaction}Action`]: {
// This will load the user for the specific action. We'll limit this to the
// admin users only or the current logged in user.
user({user_id}, _, {loaders: {Users}, user}) {
user({ user_id }, _, { loaders: { Users }, user }) {
if (user && (user.can(SEARCH_OTHER_USERS) || user_id === user.id)) {
return Users.getByID.load(user_id);
}
}
},
},
RootMutation: {
[`create${Reaction}Action`]: async (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
[`create${Reaction}Action`]: async (
_,
{ input: { item_id } },
{ mutators: { Action }, pubsub, loaders: { Comments } }
) => {
const comment = await Comments.get.load(item_id);
if (!comment) {
throw errors.ErrNotFound;
}
try {
const action = await Action.create({item_id, item_type: 'COMMENTS', action_type: REACTION});
const action = await Action.create({
item_id,
item_type: 'COMMENTS',
action_type: REACTION,
});
if (pubsub) {
// The comment is needed to allow better filtering e.g. by asset_id.
pubsub.publish(`${reaction}ActionCreated`, {action, comment});
pubsub.publish(`${reaction}ActionCreated`, { action, comment });
}
return {
@@ -203,17 +218,20 @@ function getReactionConfig(reaction) {
throw err;
}
},
[`delete${Reaction}Action`]: async (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
const action = await Action.delete({id});
[`delete${Reaction}Action`]: async (
_,
{ input: { id } },
{ mutators: { Action }, pubsub, loaders: { Comments } }
) => {
const action = await Action.delete({ id });
if (!action) {
return null;
}
const comment = await Comments.get.load(action.item_id);
if (pubsub) {
// The comment is needed to allow better filtering e.g. by asset_id.
pubsub.publish(`${reaction}ActionDeleted`, {action, comment});
pubsub.publish(`${reaction}ActionDeleted`, { action, comment });
}
},
},
@@ -221,34 +239,38 @@ function getReactionConfig(reaction) {
hooks: {
Action: {
__resolveType: {
post({action_type}) {
post({ action_type }) {
switch (action_type) {
case REACTION:
return `${Reaction}Action`;
case REACTION:
return `${Reaction}Action`;
default:
return undefined;
}
}
}
},
},
},
ActionSummary: {
__resolveType: {
post({action_type}) {
post({ action_type }) {
switch (action_type) {
case REACTION:
return `${Reaction}ActionSummary`;
case REACTION:
return `${Reaction}ActionSummary`;
default:
return undefined;
}
}
}
}
},
},
},
},
setupFunctions: {
[`${reaction}ActionCreated`]: (options, args) => ({
[`${reaction}ActionCreated`]: {
filter: ({comment}) => comment.asset_id === args.asset_id,
filter: ({ comment }) => comment.asset_id === args.asset_id,
},
}),
[`${reaction}ActionDeleted`]: (options, args) => ({
[`${reaction}ActionDeleted`]: {
filter: ({comment}) => comment.asset_id === args.asset_id,
filter: ({ comment }) => comment.asset_id === args.asset_id,
},
}),
},