@@ -42,6 +38,7 @@ const CreateUsernameDialog = ({open, handleClose, offset, formData, handleSubmit
this.emailInput = input}
type="text"
+ style={{fontSize: 16}}
id="email"
name="email" />
diff --git a/client/coral-sign-in/components/SignDialog.js b/client/coral-sign-in/components/SignDialog.js
index 6243472b4..ff2464f7c 100644
--- a/client/coral-sign-in/components/SignDialog.js
+++ b/client/coral-sign-in/components/SignDialog.js
@@ -6,15 +6,11 @@ import SignInContent from './SignInContent';
import SignUpContent from './SignUpContent';
import ForgotContent from './ForgotContent';
-const SignDialog = ({open, view, handleClose, offset, ...props}) => (
+const SignDialog = ({open, view, handleClose, ...props}) => (
+ open={open}>
×
{view === 'SIGNIN' && }
{view === 'SIGNUP' && }
diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js
index a0ed5b5b4..7460efb97 100644
--- a/client/coral-sign-in/components/SignInContent.js
+++ b/client/coral-sign-in/components/SignInContent.js
@@ -20,8 +20,8 @@ const SignInContent = ({
}) => {
return (
-
-
+
+
{auth.emailVerificationFailure ? lang.t('signIn.emailVerifyCTA') : lang.t('signIn.signIn')}
@@ -42,7 +42,7 @@ const SignInContent = ({
{emailVerificationSuccess &&
}
:
-
+
{lang.t('signIn.facebookSignIn')}
@@ -58,6 +58,7 @@ const SignInContent = ({
type="email"
label={lang.t('signIn.email')}
value={formData.email}
+ style={{fontSize: 16}}
onChange={handleChange}
/>
@@ -80,7 +82,7 @@ const SignInContent = ({
}
-
+
changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')}
{lang.t('signIn.needAnAccount')}
diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js
index d127cc2c7..921f045be 100644
--- a/client/coral-sign-in/components/SignUpContent.js
+++ b/client/coral-sign-in/components/SignUpContent.js
@@ -83,6 +83,7 @@ class SignUpContent extends React.Component {
type="email"
label={lang.t('signIn.email')}
value={formData.email}
+ style={{fontSize: 16}}
showErrors={showErrors}
errorMsg={errors.email}
onChange={handleChange}
@@ -93,6 +94,7 @@ class SignUpContent extends React.Component {
label={lang.t('signIn.username')}
value={formData.username}
showErrors={showErrors}
+ style={{fontSize: 16}}
errorMsg={errors.username}
onChange={handleChange}
/>
@@ -102,6 +104,7 @@ class SignUpContent extends React.Component {
label={lang.t('signIn.password')}
value={formData.password}
showErrors={showErrors}
+ style={{fontSize: 16}}
errorMsg={errors.password}
onChange={handleChange}
minLength="8"
@@ -112,6 +115,7 @@ class SignUpContent extends React.Component {
type="password"
label={lang.t('signIn.confirmPassword')}
value={formData.confirmPassword}
+ style={{fontSize: 16}}
showErrors={showErrors}
errorMsg={errors.confirmPassword}
onChange={handleChange}
diff --git a/client/coral-sign-in/containers/ChangeUsernameContainer.js b/client/coral-sign-in/containers/ChangeUsernameContainer.js
index c4450aa56..a3b4b969d 100644
--- a/client/coral-sign-in/containers/ChangeUsernameContainer.js
+++ b/client/coral-sign-in/containers/ChangeUsernameContainer.js
@@ -100,12 +100,11 @@ class ChangeUsernameContainer extends Component {
}
render() {
- const {loggedIn, auth, offset} = this.props;
+ const {loggedIn, auth} = this.props;
return (
- {!noButton &&
- Sign in to comment
- }
({
fetchSignUpFacebook: () => dispatch(fetchSignUpFacebook()),
fetchForgotPassword: formData => dispatch(fetchForgotPassword(formData)),
requestConfirmEmail: (email, url) => dispatch(requestConfirmEmail(email, url)),
- showSignInDialog: () => dispatch(showSignInDialog()),
changeView: view => dispatch(changeView(view)),
handleClose: () => dispatch(hideSignInDialog()),
invalidForm: error => dispatch(invalidForm(error)),
diff --git a/graph/hooks.js b/graph/hooks.js
index c5b7541b3..7f6be7feb 100644
--- a/graph/hooks.js
+++ b/graph/hooks.js
@@ -53,6 +53,71 @@ const forEachField = (schema, fn) => {
});
};
+/**
+ * Decorates the field with the post resolvers (if available) and attaches a
+ * default type in the form of `Default${typeName}`.
+ */
+const decorateResolveFunction = (field, typeName, fieldName, post) => {
+
+ // Cache the original resolverType function.
+ let resolveType = field.resolveType;
+
+ // defaultResolveType is the default type that is resolved on a resolver
+ // when the interface being looked up is not defined.
+ const defaultResolveType = `Default${typeName}`;
+
+ // Return the function to handle the resolveType hooks.
+ const defaultResolveFn = (obj, context, info) => {
+ let type = resolveType(obj, context, info);
+
+ // Only if a previous resolver was unable to resolve the field type do we
+ // progress to the hooks (in order!) to resolve the field name until we
+ // have resolved it.
+ if (typeof type !== 'undefined' && type != null) {
+ return type;
+ }
+
+ // All else fails, resort to the defaultResolveType.
+ return defaultResolveType;
+ };
+
+ // This only needs to do something if post hooks are defined.
+ if (post.length === 0) {
+
+ // Set the default on the resolveType function.
+ field.resolveType = defaultResolveFn;
+
+ return;
+ }
+
+ // Ensure it matches the format we expect.
+ Joi.assert(post, Joi.array().items(Joi.func().maxArity(3)), `invalid post hooks were found for ${typeName}.${fieldName}`);
+
+ // Return the function to handle the resolveType hooks.
+ field.resolveType = (obj, context, info) => {
+ let type = defaultResolveFn(obj, context, info);
+
+ // Only if a previous resolver was unable to resolve the field type do we
+ // progress to the hooks (in order!) to resolve the field name until we
+ // have resolved it.
+ if (typeof type !== 'undefined' && type != null && type !== defaultResolveType) {
+ return type;
+ }
+
+ // We will walk through the post hooks until we find the right one. This
+ // follows what redux does to combine existing reducers.
+ for (let i = 0; i < post.length; i++) {
+ let resolveType = post[i];
+ let resolvedType = resolveType(obj, context, info);
+ if (typeof resolvedType !== 'undefined' && resolvedType != null) {
+ return resolvedType;
+ }
+ }
+
+ return type;
+ };
+};
+
/**
* Decorates the schema with pre and post hooks as provided by the Plugin
* Manager.
@@ -115,11 +180,6 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa
post: []
});
- // If we have no hooks to add here, don't try to modify anything.
- if (pre.length === 0 && post.length === 0) {
- return;
- }
-
// If this is a resolve type, we need to do some specific things to handle
// this type of field.
if (isResolveType) {
@@ -129,39 +189,13 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa
throw new Error(`invalid pre hooks were found for ${typeName}.${fieldName}, only post hooks are supported on the __resolveType hook`);
}
- // This only needs to do something if post hooks are defined.
- if (post.length === 0) {
- return;
- }
-
- // Ensure it matches the format we expect.
- Joi.assert(post, Joi.array().items(Joi.func().maxArity(3)), `invalid post hooks were found for ${typeName}.${fieldName}`);
-
- // Cache the original resolverType function.
- let resolveType = field.resolveType;
-
- // Return the function to handle the resolveType hooks.
- field.resolveType = (obj, context, info) => {
- let type = resolveType(obj, context, info);
-
- // Only if a previous resolver was unable to resolve the field type do we
- // progress to the hooks (in order!) to resolve the field name until we
- // have resolved it.
- if (typeof type !== 'undefined' && type != null) {
- return type;
- }
-
- // We will walk through the post hooks until we find the right one. This
- // follows what redux does to combine existing reducers.
- for (let i = 0; i < post.length; i++) {
- let resolveType = post[i];
- type = resolveType(obj, context, info);
- if (typeof type !== 'undefined' && type != null) {
- return type;
- }
- }
- };
+ // Decorate the resolve function on the field with the new resolveType func.
+ decorateResolveFunction(field, typeName, fieldName, post);
+ return;
+ }
+ // If we have no hooks to add here, don't try to modify anything.
+ if (pre.length === 0 && post.length === 0) {
return;
}
diff --git a/graph/resolvers/action_summary.js b/graph/resolvers/action_summary.js
index 1986a0648..ac2154de8 100644
--- a/graph/resolvers/action_summary.js
+++ b/graph/resolvers/action_summary.js
@@ -8,7 +8,7 @@ const ActionSummary = {
case 'DONTAGREE':
return 'DontAgreeActionSummary';
}
- },
+ }
};
module.exports = ActionSummary;
diff --git a/graph/resolvers/flag_action.js b/graph/resolvers/flag_action.js
index 80d1583ad..e4b30c408 100644
--- a/graph/resolvers/flag_action.js
+++ b/graph/resolvers/flag_action.js
@@ -8,7 +8,9 @@ const FlagAction = {
return group_id;
},
user({user_id}, _, {loaders: {Users}}) {
- return Users.getByID.load(user_id);
+ if (user_id) {
+ return Users.getByID.load(user_id);
+ }
},
};
diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js
index 8de1988ff..4dcb7ea11 100644
--- a/graph/resolvers/root_query.js
+++ b/graph/resolvers/root_query.js
@@ -84,9 +84,10 @@ const RootQuery = {
},
myIgnoredUsers: async (_, args, {user, loaders: {Users}}) => {
- if (user == null) {
- return [];
+ if (!user) {
+ return null;
}
+
// get currentUser again since context.user was out of date when running test/graph/mutations/ignoreUser
const currentUser = (await Users.getByQuery({ids: [user.id], limit: 1}))[0];
if ( ! (currentUser && Array.isArray(currentUser.ignoresUsers) && currentUser.ignoresUsers.length)) {
diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql
index 0a067b49c..32c2c16c2 100644
--- a/graph/typeDefs.graphql
+++ b/graph/typeDefs.graphql
@@ -31,7 +31,7 @@ type User {
username: String!
# Action summaries against the user.
- action_summaries: [ActionSummary]
+ action_summaries: [ActionSummary]!
# Actions completed on the parent.
actions: [Action]
@@ -197,7 +197,7 @@ type Comment {
actions: [Action]
# Action summaries against a comment.
- action_summaries: [ActionSummary]
+ action_summaries: [ActionSummary]!
# The asset that a comment was made on.
asset: Asset
@@ -229,6 +229,22 @@ interface Action {
created_at: Date
}
+# DefaultAction is the Action provided for undefined types.
+type DefaultAction implements Action {
+
+ # The ID of the action.
+ id: ID!
+
+ # The author of the action.
+ user: User
+
+ # The time when the Action was updated.
+ updated_at: Date
+
+ # The time when the Action was created.
+ created_at: Date
+}
+
# A summary of actions based on the specific grouping of the group_id.
interface ActionSummary {
@@ -239,6 +255,16 @@ interface ActionSummary {
current_user: Action
}
+# DefaultActionSummary is the ActionSummary provided for undefined types.
+type DefaultActionSummary implements ActionSummary {
+
+ # The count of actions with this group.
+ count: Int
+
+ # The current user's action.
+ current_user: Action
+}
+
# A summary of actions for a specific action type on an Asset.
interface AssetActionSummary {
@@ -249,6 +275,16 @@ interface AssetActionSummary {
actionableItemCount: Int
}
+# DefaultAssetActionSummary is the AssetActionSummary provided for undefined types.
+type DefaultAssetActionSummary implements AssetActionSummary {
+
+ # Number of actions associated with actionable types on this this Asset.
+ actionCount: Int
+
+ # Number of unique actionable types that are referenced by the actions.
+ actionableItemCount: Int
+}
+
# A summary of counts related to all the Flags on an Asset.
type FlagAssetActionSummary implements AssetActionSummary {
@@ -440,7 +476,7 @@ type Asset {
# Summary of all Actions against all entities associated with the Asset.
# (likes, flags, etc.). Requires the `ADMIN` role.
- action_summaries: [AssetActionSummary]
+ action_summaries: [AssetActionSummary!]
# The date that the asset was created.
created_at: Date
diff --git a/package.json b/package.json
index 1b09c78c8..6c725a852 100644
--- a/package.json
+++ b/package.json
@@ -182,6 +182,6 @@
"webpack": "^2.3.1"
},
"engines": {
- "node": "^7.9.0"
+ "node": "^7.8.0"
}
}
diff --git a/plugins/coral-plugin-respect/client/components/RespectButton.js b/plugins/coral-plugin-respect/client/components/RespectButton.js
index 9e851460e..c1e0c1b47 100644
--- a/plugins/coral-plugin-respect/client/components/RespectButton.js
+++ b/plugins/coral-plugin-respect/client/components/RespectButton.js
@@ -5,6 +5,7 @@ import Icon from './Icon';
import {I18n} from 'coral-framework';
import cn from 'classnames';
import translations from '../translations.json';
+import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
const lang = new I18n(translations);
@@ -14,13 +15,11 @@ class RespectButton extends Component {
const {postRespect, showSignInDialog, deleteAction, commentId} = this.props;
const {me, comment} = this.props.data;
- const respect = comment.action_summaries[0];
- const respected = (respect && respect.current_user);
+ const myRespectActionSummary = getMyActionSummary('RespectActionSummary', comment);
// If the current user does not exist, trigger sign in dialog.
if (!me) {
- const offset = document.getElementById(`c_${commentId}`).getBoundingClientRect().top - 75;
- showSignInDialog(offset);
+ showSignInDialog();
return;
}
@@ -29,29 +28,33 @@ class RespectButton extends Component {
return;
}
- if (!respected) {
+ if (myRespectActionSummary) {
+ deleteAction(myRespectActionSummary.current_user.id);
+ } else {
postRespect({
item_id: commentId,
item_type: 'COMMENTS'
});
- } else {
- deleteAction(respect.current_user.id);
}
}
render() {
const {comment} = this.props.data;
- const respect = comment && comment.action_summaries && comment.action_summaries[0];
- const respected = respect && respect.current_user;
- let count = respect ? respect.count : 0;
+
+ if (!comment) {
+ return null;
+ }
+
+ const myRespect = getMyActionSummary('RespectActionSummary', comment);
+ let count = getTotalActionCount('RespectActionSummary', comment);
return (
- {lang.t(respected ? 'respected' : 'respect')}
-
+ {lang.t(myRespect ? 'respected' : 'respect')}
+
{count > 0 && count}
@@ -64,4 +67,3 @@ RespectButton.propTypes = {
};
export default RespectButton;
-
diff --git a/plugins/coral-plugin-respect/client/containers/RespectButton.js b/plugins/coral-plugin-respect/client/containers/RespectButton.js
index 047f7991f..bcf0e342d 100644
--- a/plugins/coral-plugin-respect/client/containers/RespectButton.js
+++ b/plugins/coral-plugin-respect/client/containers/RespectButton.js
@@ -10,6 +10,8 @@ import RespectButton from '../components/RespectButton';
// See https://dev-blog.apollodata.com/apollo-clients-new-imperative-store-api-6cb69318a1e3
// and https://github.com/apollographql/apollo-client/issues/1224
+const isRespectAction = (a) => a.__typename === 'RespectActionSummary';
+
export const RESPECT_QUERY = gql`
query RespectQuery($commentId: ID!) {
comment(id: $commentId) {
@@ -52,18 +54,21 @@ const withDeleteAction = graphql(gql`
},
updateQueries: {
RespectQuery: (prev) => {
- if (get(prev, 'comment.action_summaries.0.current_user.id') !== id) {
+ const action_summaries = prev.comment.action_summaries;
+ const idx = action_summaries.findIndex(isRespectAction);
+ if (idx < 0 || get(action_summaries[idx], 'current_user.id') !== id) {
return prev;
}
const next = {
...prev,
comment: {
...prev.comment,
- action_summaries: [{
- __typename: 'RespectActionSummary',
- count: prev.comment.action_summaries[0].count - 1,
- current_user: null,
- }],
+ action_summaries: action_summaries.map(
+ (a, i) => i !== idx ? a : ({
+ ...a,
+ count: a.count - 1,
+ current_user: null,
+ })),
}
};
return next;
@@ -102,21 +107,40 @@ const withPostRespect = graphql(gql`
},
updateQueries: {
RespectQuery: (prev, {mutationResult, queryVariables}) => {
- if (queryVariables.commentId !== respect.item_id ||
- get(prev, 'comment.action_summaries.0.current_user')) {
+ if (queryVariables.commentId !== respect.item_id) {
return prev;
}
+
+ let action_summaries = prev.comment.action_summaries;
+ let idx = action_summaries.findIndex(isRespectAction);
+
+ // Check whether we already respected this comment.
+ if (idx >= 0 && action_summaries[idx].current_user) {
+ return prev;
+ }
+
+ if (idx < 0) {
+
+ // Add initial action when it doesn't exist.
+ action_summaries = action_summaries.concat([{
+ __typename: 'RespectActionSummary',
+ count: 0,
+ current_user: null,
+ }]);
+ idx = action_summaries.length - 1;
+ }
+
const respectAction = mutationResult.data.createRespect.respect;
- const count = prev.comment.action_summaries[0] ? prev.comment.action_summaries[0].count : 0;
const next = {
...prev,
comment: {
...prev.comment,
- action_summaries: [{
- __typename: 'RespectActionSummary',
- count: count + 1,
- current_user: respectAction,
- }],
+ action_summaries: action_summaries.map(
+ (a, i) => i !== idx ? a : ({
+ ...a,
+ count: a.count + 1,
+ current_user: respectAction,
+ })),
}
};
return next;
@@ -138,4 +162,3 @@ const enhance = compose(
);
export default enhance(RespectButton);
-
diff --git a/services/actions.js b/services/actions.js
index 0bcb81af0..40bef65bb 100644
--- a/services/actions.js
+++ b/services/actions.js
@@ -48,10 +48,16 @@ module.exports = class ActionsService {
* Finds actions in an array of ids.
* @param {String} ids array of user identifiers (uuid)
*/
- static findByItemIdArray(item_ids) {
- return ActionModel.find({
+ static async findByItemIdArray(item_ids) {
+ let actions = await ActionModel.find({
'item_id': {$in: item_ids}
});
+
+ if (actions === null) {
+ return [];
+ }
+
+ return actions;
}
/**
diff --git a/views/embed/stream.ejs b/views/embed/stream.ejs
index 498090e32..d99d4bce1 100644
--- a/views/embed/stream.ejs
+++ b/views/embed/stream.ejs
@@ -1,6 +1,8 @@
+
+