Merge branch 'master' into optional-google-fonts

This commit is contained in:
Riley Davis
2017-04-07 10:42:20 -06:00
61 changed files with 974 additions and 151 deletions
+2 -1
View File
@@ -3,4 +3,5 @@ client/lib
**/*.html
plugins/*
!plugins/coral-plugin-facebook-auth
node_modules
!plugins/coral-plugin-respect
node_modules
+2 -1
View File
@@ -16,4 +16,5 @@ coverage/
plugins.json
plugins/*
!plugins/coral-plugin-facebook-auth
!plugins/coral-plugin-facebook-auth
!plugins/coral-plugin-respect
+1 -1
View File
@@ -1,5 +1,5 @@
{
"verbose": true,
"ignore": ["test/*", "client/*", "dist/*"],
"ignore": ["test/*", "client/*", "dist/*", "plugins/*/client"],
"ext": "js,json,graphql"
}
+2 -2
View File
@@ -1,8 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {render} from 'react-dom';
import {GraphQLDocs} from 'graphql-docs';
import fetcher from './services/fetcher';
// Render the application into the DOM
ReactDOM.render(<GraphQLDocs fetcher={fetcher} />, document.querySelector('#root'));
render(<GraphQLDocs fetcher={fetcher} />, document.querySelector('#root'));
+5 -1
View File
@@ -19,6 +19,7 @@ import FlagComment from 'coral-plugin-flags/FlagComment';
import LikeButton from 'coral-plugin-likes/LikeButton';
import {BestButton, IfUserCanModifyBest, BEST_TAG, commentIsBest, BestIndicator} from 'coral-plugin-best/BestButton';
import LoadMore from 'coral-embed-stream/src/LoadMore';
import {Slot} from 'coral-framework';
import styles from './Comment.css';
@@ -157,6 +158,7 @@ class Comment extends React.Component {
? <TagLabel><BestIndicator /></TagLabel>
: null }
<PubDate created_at={comment.created_at} />
<Slot fill="commentInfoBar" commentId={comment.id} />
<Content body={comment.body} />
<div className="commentActionsLeft comment__action-container">
@@ -187,6 +189,7 @@ class Comment extends React.Component {
removeBest={removeBestTag} />
</IfUserCanModifyBest>
</ActionButton>
<Slot fill="commentDetail" commentId={comment.id} />
</div>
<div className="commentActionsRight comment__action-container">
<ActionButton>
@@ -241,7 +244,8 @@ class Comment extends React.Component {
showSignInDialog={showSignInDialog}
reactKey={reply.id}
key={reply.id}
comment={reply} />;
comment={reply}
/>;
})
}
{
+1 -1
View File
@@ -313,5 +313,5 @@ export default compose(
addCommentTag,
removeCommentTag,
deleteAction,
queryStream
queryStream,
)(Embed);
+5 -2
View File
@@ -41,7 +41,8 @@ class Stream extends React.Component {
deleteAction,
showSignInDialog,
addCommentTag,
removeCommentTag
removeCommentTag,
pluginProps
} = this.props;
return (
@@ -67,7 +68,9 @@ class Stream extends React.Component {
showSignInDialog={showSignInDialog}
key={comment.id}
reactKey={comment.id}
comment={comment} />
comment={comment}
pluginProps={pluginProps}
/>
)
}
</div>
+2 -2
View File
@@ -255,13 +255,13 @@ hr {
.commentActionsRight, .replyActionsRight {
display: flex;
justify-content: flex-end;
width: 50%;
width: 30%;
}
.commentActionsLeft, .replyActionsLeft {
display: flex;
justify-content: flex-start;
float: left;
width: 50%;
width: 70%;
}
.comment__action-container .material-icons {
+1 -1
View File
@@ -3,7 +3,7 @@ import coralApi from '../helpers/response';
import {addNotification} from '../actions/notification';
import {pym} from 'coral-framework';
import I18n from '../../coral-framework/modules/i18n/i18n';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
+22 -1
View File
@@ -1,3 +1,5 @@
import {gql} from 'react-apollo';
import client from 'coral-framework/services/client';
import I18n from '../../coral-framework/modules/i18n/i18n';
import translations from './../translations';
const lang = new I18n(translations);
@@ -5,6 +7,20 @@ import * as actions from '../constants/auth';
import coralApi, {base} from '../helpers/response';
import {pym} from 'coral-framework';
const ME_QUERY = gql`
query Me {
me {
status
}
}
`;
function fetchMe() {
return client.query({
fetchPolicy: 'network-only',
query: ME_QUERY});
}
// Dialog Actions
export const showSignInDialog = (offset = 0) => ({type: actions.SHOW_SIGNIN_DIALOG, offset});
export const hideSignInDialog = () => ({type: actions.HIDE_SIGNIN_DIALOG});
@@ -52,6 +68,7 @@ export const fetchSignIn = (formData) => (dispatch) => {
const isAdmin = !!user && !!user.roles.filter(i => i === 'ADMIN').length;
dispatch(signInSuccess(user, isAdmin));
dispatch(hideSignInDialog());
fetchMe();
})
.catch(error => {
if (error.metadata) {
@@ -104,6 +121,7 @@ export const facebookCallback = (err, data) => dispatch => {
dispatch(signInFacebookSuccess(user));
dispatch(hideSignInDialog());
dispatch(showCreateUsernameDialog());
fetchMe();
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
@@ -151,7 +169,10 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
export const logout = () => dispatch => {
dispatch(logOutRequest());
return coralApi('/auth', {method: 'DELETE'})
.then(() => dispatch(logOutSuccess()))
.then(() => {
dispatch(logOutSuccess());
fetchMe();
})
.catch(error => dispatch(logOutFailure(error)));
};
+9
View File
@@ -0,0 +1,9 @@
import * as authActions from './auth';
import * as assetActions from './asset';
import * as notificationActions from './notification';
export default {
authActions,
assetActions,
notificationActions,
};
+19
View File
@@ -0,0 +1,19 @@
import React, {Component} from 'react';
import {getSlotElements} from 'coral-framework/helpers/plugins';
class Slot extends Component {
render() {
const {fill, ...rest} = this.props;
return (
<div>
{getSlotElements(fill, rest)}
</div>
);
}
}
Slot.propTypes = {
fill: React.PropTypes.string
};
export default Slot;
+21
View File
@@ -0,0 +1,21 @@
import React from 'react';
import merge from 'lodash/merge';
import flatten from 'lodash/flatten';
import plugins from 'pluginsConfig';
export const pluginReducers = merge(
...plugins
.filter(o => o.module.reducer)
.map(o => ({...o.module.reducer}))
);
/**
* Returns React Elements for given slot.
*/
export function getSlotElements(slot, props = {}) {
const components = flatten(plugins
.filter(o => o.module.slots[slot])
.map(o => o.module.slots[slot]));
return components
.map((component, i) => React.createElement(component, {...props, key: i}));
}
+8 -7
View File
@@ -1,15 +1,16 @@
import store from './services/store';
import pym from './services/PymConnection';
import I18n from './modules/i18n/i18n';
import * as authActions from './actions/auth';
import * as assetActions from './actions/asset';
import * as notificationActions from './actions/notification';
import actions from './actions';
import Slot from './components/Slot';
export {
// TODO (bc): Deprecate old actions. Spreading actions is now needed.
export default {
pym,
Slot,
I18n,
store,
authActions,
assetActions,
notificationActions
actions,
...actions
};
@@ -0,0 +1,33 @@
/**
* Executes `source` to retrieve plugins configuration
* and loads the `index.js` of specified plugins.
*
* Outputs a module that looks like the following:
*
* module.exports = [{plugin: string, module: object}, ...]
*
*/
const {stripIndent} = require('common-tags');
function getPluginList(config) {
if (config && config.client) {
return config.client.map(x => typeof x === 'string' ? x : Object.keys(x)[0]);
}
return [];
}
module.exports = function(source) {
this.cacheable();
const config = this.exec(source, this.resourcePath);
const plugins = getPluginList(config).map((plugin) => `{
module: require('${plugin}/client'),
plugin: '${plugin}'
}`);
return stripIndent`
module.exports = [
${plugins.join(',')}
];
`;
};
+2
View File
@@ -1,9 +1,11 @@
import auth from './auth';
import user from './user';
import asset from './asset';
import {pluginReducers} from '../helpers/plugins';
export default {
auth,
user,
asset,
...pluginReducers
};
+3 -1
View File
@@ -6,9 +6,11 @@ export const client = new ApolloClient({
queryTransformer: addTypename,
dataIdFromObject: (result) => {
if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle
return result.__typename + result.id; // eslint-disable-line no-underscore-dangle
return `${result.__typename}_${result.id}`; // eslint-disable-line no-underscore-dangle
}
return null;
},
networkInterface: getNetworkInterface()
});
export default client;
+7
View File
@@ -0,0 +1,7 @@
/**
* getActionSummary
* retrieves the action summary based on the type and the comment
*/
export const getActionSummary = (type, comment) =>
comment.action_summaries.filter(a => a.__typename === type)[0];
+31
View File
@@ -0,0 +1,31 @@
const errors = require('../../errors');
const {Error: {ValidationError}} = require('mongoose');
/**
* Wraps up a promise to return an object with the resolution of the promise
* keyed at `key` or an error caught at `errors`.
*/
const wrapResponse = (key) => (promise) => {
return promise.then((value) => {
let res = {};
if (key) {
res[key] = value;
}
return res;
}).catch((err) => {
if (err instanceof errors.APIError) {
return {
errors: [err]
};
} else if (err instanceof ValidationError) {
// TODO: wrap this with one of our internal errors.
throw err;
}
throw err;
});
};
module.exports = wrapResponse;
+75 -3
View File
@@ -1,4 +1,7 @@
const {forEachField} = require('graphql-tools');
const {
GraphQLObjectType,
GraphQLInterfaceType
} = require('graphql');
const debug = require('debug')('talk:graph:schema');
/**
@@ -22,6 +25,33 @@ const defaultResolveFn = (source, args, context, {fieldName}) => {
}
};
// This function is pretty much copied verbatim from the graphql-tools repo:
// https://github.com/apollographql/graphql-tools/blob/b12973c86e00be209d04af0184780998056051c4/src/schemaGenerator.ts#L180-L194
// With the small alteration that we look for the `resolveType` function on the
// schema so we can wrap post hooks around it to provide additional resolve
// points.
const forEachField = (schema, fn) => {
const typeMap = schema.getTypeMap();
Object.keys(typeMap).forEach((typeName) => {
const type = typeMap[typeName];
if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) {
// Here we capture the change to extract the resolve type. We pass this
// with the `isResolveType = true` to introduce the specific beheviour.
if ('resolveType' in type) {
fn(type, typeName, '__resolveType', true);
}
const fields = type.getFields();
Object.keys(fields).forEach((fieldName) => {
const field = fields[fieldName];
fn(field, typeName, fieldName);
});
}
});
};
/**
* Decorates the schema with pre and post hooks as provided by the Plugin
* Manager.
@@ -29,7 +59,7 @@ const defaultResolveFn = (source, args, context, {fieldName}) => {
* @param {Array} hooks hooks to apply to the schema
* @return {void}
*/
const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeName, fieldName) => {
const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeName, fieldName, isResolveType = false) => {
// Pull out the pre/post hooks from the available hooks.
const {
@@ -85,6 +115,48 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa
return;
}
// If this is a resolve type, we need to do some specific things to handle
// this type of field.
if (isResolveType) {
// Warn if we have any pre hooks.
if (pre.length !== 0) {
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;
}
// 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;
}
}
};
return;
}
// Cache the original resolve function, this emulates the beheviour found in
// graphql-tools: https://github.com/apollographql/graphql-tools/blob/6e9cc124b10d673448386041e6c3d058bc205a02/src/schemaGenerator.ts#L423-L425
let resolve = field.resolve;
@@ -102,7 +174,7 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa
await Promise.all(pre.map((pre) => pre(obj, args, context, info)));
// Resolve the field.
let result = resolve(obj, args, context, info);
let result = await resolve(obj, args, context, info);
// Insure all post hooks after we've resolved the field with the result
// passed in as the fifth argument.
+1 -28
View File
@@ -1,33 +1,6 @@
const {Error: {ValidationError}} = require('mongoose');
const errors = require('../../errors');
const wrapResponse = require('../helpers/response');
const CommentsService = require('../../services/comments');
/**
* Wraps up a promise to return an object with the resolution of the promise
* keyed at `key` or an error caught at `errors`.
*/
const wrapResponse = (key) => (promise) => {
return promise.then((value) => {
let res = {};
if (key) {
res[key] = value;
}
return res;
}).catch((err) => {
if (err instanceof errors.APIError) {
return {
errors: [err]
};
} else if (err instanceof ValidationError) {
// TODO: wrap this with one of our internal errors.
throw err;
}
throw err;
});
};
const RootMutation = {
createComment(_, {asset_id, parent_id, body}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.create({asset_id, parent_id, body}));
+7 -3
View File
@@ -7,7 +7,7 @@
"postinstall": "./bin/cli plugins reconcile --skip-remote",
"start": "./bin/cli serve --jobs",
"dev-start": "nodemon --config .nodemon.json --exec \"./bin/cli -c .env serve --jobs\"",
"build": "NODE_ENV=production webpack --progress -p --config webpack.config.js --bail",
"build": "NODE_ENV=production webpack -p --config webpack.config.js --bail",
"build-watch": "NODE_ENV=development webpack --progress --config webpack.config.js --watch",
"lint": "eslint bin/* .",
"lint-fix": "eslint bin/* . --fix",
@@ -74,6 +74,7 @@
"graphql-tools": "^0.9.0",
"helmet": "^3.5.0",
"inquirer": "^3.0.6",
"joi": "^10.4.1",
"jsonwebtoken": "^7.3.0",
"kue": "^0.11.5",
"linkify-it": "^2.0.3",
@@ -90,7 +91,7 @@
"parse-duration": "^0.1.1",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"react-apollo": "^0.10.0",
"react-apollo": "^1.0.0",
"react-recaptcha": "^2.2.6",
"redis": "^2.7.1",
"resolve": "^1.3.2",
@@ -99,8 +100,9 @@
"uuid": "^2.0.3"
},
"devDependencies": {
"apollo-client": "^0.8.3",
"apollo-client": "^1.0.0",
"autoprefixer": "^6.5.2",
"babel-cli": "^6.24.0",
"babel-core": "^6.24.0",
"babel-eslint": "^7.2.1",
"babel-jest": "^19.0.0",
@@ -114,10 +116,12 @@
"babel-plugin-transform-react-jsx": "^6.23.0",
"babel-polyfill": "^6.23.0",
"babel-preset-es2015": "^6.24.0",
"babel-preset-react": "^6.23.0",
"babel-preset-stage-0": "^6.16.0",
"chai": "^3.5.0",
"chai-as-promised": "^6.0.0",
"chai-http": "^3.0.0",
"common-tags": "^1.4.0",
"copy-webpack-plugin": "^4.0.0",
"css-loader": "^0.27.3",
"dialog-polyfill": "^0.4.4",
+4
View File
@@ -1,5 +1,9 @@
{
"server": [
"coral-plugin-respect",
"coral-plugin-facebook-auth"
],
"client": [
"coral-plugin-respect"
]
}
+1
View File
@@ -0,0 +1 @@
module.exports = JSON.parse(process.env.TALK_PLUGINS_JSON);
+46 -6
View File
@@ -2,9 +2,11 @@ const fs = require('fs');
const path = require('path');
const resolve = require('resolve');
const debug = require('debug')('talk:plugins');
const Joi = require('joi');
const amp = require('app-module-path');
// Add support for require rewriting.
require('app-module-path').addPath(__dirname);
// Add the current path to the module root.
amp.addPath(__dirname);
let plugins = {};
@@ -12,13 +14,13 @@ let plugins = {};
// file isn't loaded, but continuing. Else, like a parsing error, throw it and
// crash the program.
try {
let defaultPlugins = path.join(__dirname, 'plugins.default.json');
let envPlugins = path.join(__dirname, 'plugins.env.js');
let customPlugins = path.join(__dirname, 'plugins.json');
let envPluginJSON = process.env.TALK_PLUGINS_JSON;
let defaultPlugins = path.join(__dirname, 'plugins.default.json');
if (envPluginJSON && envPluginJSON.length > 0) {
if (process.env.TALK_PLUGINS_JSON && process.env.TALK_PLUGINS_JSON.length > 0) {
debug('Now using TALK_PLUGINS_JSON environment variable for plugins');
plugins = JSON.parse(envPluginJSON);
plugins = require(envPlugins);
} else if (fs.existsSync(customPlugins)) {
debug(`Now using ${customPlugins} for plugins`);
plugins = JSON.parse(fs.readFileSync(customPlugins, 'utf8'));
@@ -34,6 +36,23 @@ try {
}
}
/**
* All the hooks from plugins must match the schema defined here.
*/
const hookSchemas = {
passport: Joi.func().arity(1),
router: Joi.func().arity(1),
context: Joi.object().pattern(/\w/, Joi.func().maxArity(1)),
hooks: Joi.object().pattern(/\w/, Joi.object().pattern(/(?:__resolveType|\w+)/, Joi.object({
pre: Joi.func(),
post: Joi.func()
}))),
loaders: Joi.object().pattern(/\w/, Joi.object().pattern(/\w/, Joi.func())),
mutators: Joi.object().pattern(/\w/, Joi.object().pattern(/\w/, Joi.func())),
resolvers: Joi.object().pattern(/\w/, Joi.object().pattern(/(?:__resolveType|\w+)/, Joi.func())),
typeDefs: Joi.string()
};
/**
* isInternal checks to see if a given plugin is internal, and returns true
* if it is.
@@ -67,6 +86,12 @@ function pluginPath(name) {
}
}
/**
* Itterates over the plugins and gets the plugin path's, version, and name.
*
* @param {Array<Object|String>} plugins
* @returns {Array<Object>}
*/
function itteratePlugins(plugins) {
return plugins.map((p) => {
let plugin = {};
@@ -96,6 +121,12 @@ function itteratePlugins(plugins) {
});
}
// Add each plugin folder to the allowed import path so that they can import our
// internal dependancies.
Object.keys(plugins).forEach((type) => itteratePlugins(plugins[type]).forEach((plugin) => {
amp.enableForDir(path.dirname(plugin.path));
}));
/**
* Stores a reference to a section for a section of Plugins.
*/
@@ -135,6 +166,15 @@ class PluginSection {
hook(hook) {
return this.plugins
.filter(({module}) => hook in module)
.filter((plugin) => {
// Validate the hook.
if (hook in hookSchemas) {
Joi.assert(plugin.module[hook], hookSchemas[hook], `Plugin '${plugin.name}' failed validation for the '${hook}' hook`);
}
return true;
})
.map((plugin) => ({
plugin,
[hook]: plugin.module[hook]
@@ -0,0 +1,14 @@
{
"presets": [
"es2015"
],
"plugins": [
"add-module-exports",
"transform-class-properties",
"transform-decorators-legacy",
"transform-object-assign",
"transform-object-rest-spread",
"transform-async-to-generator",
"transform-react-jsx"
]
}
@@ -0,0 +1,23 @@
{
"env": {
"browser": true,
"es6": true,
"mocha": true
},
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"parser": "babel-eslint",
"plugins": [
"react"
],
"rules": {
"react/jsx-uses-react": "error",
"react/jsx-uses-vars": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
}
}
@@ -0,0 +1,6 @@
import React from 'react';
import cn from 'classnames';
export default ({className}) => (
<i className={cn('fa', 'fa-handshake-o', className)} aria-hidden="true"/>
);
@@ -0,0 +1,67 @@
import React, {Component} from 'react';
import styles from './style.css';
import Icon from './Icon';
import {I18n} from 'coral-framework';
import cn from 'classnames';
import translations from '../translations.json';
const lang = new I18n(translations);
class RespectButton extends Component {
handleClick = () => {
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);
// If the current user does not exist, trigger sign in dialog.
if (!me) {
const offset = document.getElementById(`c_${commentId}`).getBoundingClientRect().top - 75;
showSignInDialog(offset);
return;
}
// If the current user is banned, do nothing.
if (me.status === 'BANNED') {
return;
}
if (!respected) {
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;
return (
<div className={styles.respect}>
<button
className={cn(styles.button, {[styles.respected]: respected})}
onClick={this.handleClick} >
<span>{lang.t(respected ? 'respected' : 'respect')}</span>
<Icon className={cn(styles.icon, {[styles.respected]: respected})} />
{count > 0 && count}
</button>
</div>
);
}
}
RespectButton.propTypes = {
data: React.PropTypes.object.isRequired
};
export default RespectButton;
@@ -0,0 +1,30 @@
.respect {
display: inline-block;
}
.button {
color: #2a2a2a;
margin: 5px 10px 5px 0px;
background: none;
padding: 0px;
border: none;
font-size: inherit;
&:hover {
color: #767676;
cursor: pointer;
}
&.respected {
color: #c98211;
&:hover {
color: #e59614;
cursor: pointer;
}
}
}
.icon {
padding: 0 5px;
}
@@ -0,0 +1,140 @@
import {compose, gql, graphql} from 'react-apollo';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import get from 'lodash/get';
import {showSignInDialog} from 'coral-framework/actions/auth';
import RespectButton from '../components/RespectButton';
// TODO: use `update` instead of `updateQueries` for optimistic mutations.
// See https://dev-blog.apollodata.com/apollo-clients-new-imperative-store-api-6cb69318a1e3
// and https://github.com/apollographql/apollo-client/issues/1224
export const RESPECT_QUERY = gql`
query RespectQuery($commentId: ID!) {
comment(id: $commentId) {
id
action_summaries {
... on RespectActionSummary {
count
current_user {
id
}
}
}
}
me {
status
}
}
`;
const withQuery = graphql(RESPECT_QUERY);
const withDeleteAction = graphql(gql`
mutation deleteAction($id: ID!) {
deleteAction(id:$id) {
errors {
translation_key
}
}
}
`, {
props: ({mutate}) => ({
deleteAction: (id) => {
return mutate({
variables: {id},
optimisticResponse: {
deleteAction: {
__typename: 'DeleteActionResponse',
errors: null,
}
},
updateQueries: {
RespectQuery: (prev) => {
if (get(prev, 'comment.action_summaries.0.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,
}],
}
};
return next;
},
},
});
},
}),
});
const withPostRespect = graphql(gql`
mutation createRespect($respect: CreateRespectInput!) {
createRespect(respect: $respect) {
respect {
id
}
errors {
translation_key
}
}
}
`, {
props: ({mutate}) => ({
postRespect: (respect) => {
return mutate({
variables: {respect},
optimisticResponse: {
createRespect: {
__typename: 'CreateRespectResponse',
errors: null,
respect: {
__typename: 'RespectAction',
id: 'pending',
},
}
},
updateQueries: {
RespectQuery: (prev, {mutationResult, queryVariables}) => {
if (queryVariables.commentId !== respect.item_id) {
return prev;
}
const respectAction = mutationResult.data.createRespect.respect;
const count = prev.action_summaries ? prev.action_summaries.count : 0;
const next = {
...prev,
comment: {
...prev.comment,
action_summaries: [{
__typename: 'RespectActionSummary',
count: count + 1,
current_user: respectAction,
}],
}
};
return next;
},
},
});
},
}),
});
const mapDispatchToProps = dispatch =>
bindActionCreators({showSignInDialog}, dispatch);
const enhance = compose(
connect(null, mapDispatchToProps),
withDeleteAction,
withPostRespect,
withQuery,
);
export default enhance(RespectButton);
@@ -0,0 +1,6 @@
import RespectButton from './containers/RespectButton';
export default {
slots: {
commentDetail: [RespectButton],
}
};
@@ -0,0 +1,10 @@
{
"en": {
"respect": "Respect",
"respected": "Respected"
},
"es": {
"respect": "Respeto",
"respected": "Respetado"
}
}
+36
View File
@@ -0,0 +1,36 @@
const {readFileSync} = require('fs');
const path = require('path');
const wrapResponse = require('../../graph/helpers/response');
module.exports = {
typeDefs: readFileSync(path.join(__dirname, 'server/typeDefs.graphql'), 'utf8'),
resolvers: {
RootMutation: {
createRespect(_, {respect: {item_id, item_type}}, {mutators: {Action}}) {
return wrapResponse('respect')(Action.create({item_id, item_type, action_type: 'RESPECT'}));
}
}
},
hooks: {
Action: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'RESPECT':
return 'RespectAction';
}
}
}
},
ActionSummary: {
__resolveType: {
post({action_type}) {
switch (action_type) {
case 'RESPECT':
return 'RespectActionSummary';
}
}
}
}
}
};
@@ -0,0 +1,54 @@
enum ACTION_TYPE {
# Represents a Respect.
RESPECT
}
input CreateRespectInput {
# The item's id for which we are to create a respect.
item_id: ID!
# The type of the item for which we are to create the respect.
item_type: ACTION_ITEM_TYPE!
}
# RespectAction is used by users who "respect" a specific entity.
type RespectAction 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
}
type RespectActionSummary implements ActionSummary {
# The count of actions with this group.
count: Int
# The current user's action.
current_user: RespectAction
}
type CreateRespectResponse implements Response {
# The respect that was created.
respect: RespectAction
# An array of errors relating to the mutation that occurred.
errors: [UserError]
}
type RootMutation {
# Creates a respect on an entity.
createRespect(respect: CreateRespectInput!): CreateRespectResponse
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"extends": "../../client/.babelrc"
"extends": "../../.babelrc"
}
+3 -1
View File
@@ -1,5 +1,7 @@
test/helpers/*.js
test
test/e2e
test/server
--compilers js:babel-core/register
--require ignore-styles
--recursive
@@ -1,8 +1,8 @@
const expect = require('chai').expect;
const User = require('../../models/user');
const Context = require('../../graph/context');
const errors = require('../../errors');
const User = require('../../../models/user');
const Context = require('../../../graph/context');
const errors = require('../../../errors');
describe('graph.Context', () => {
@@ -1,13 +1,13 @@
const {expect} = require('chai');
const {graphql} = require('graphql');
const schema = require('../../../graph/schema');
const Context = require('../../../graph/context');
const UserModel = require('../../../models/user');
const AssetModel = require('../../../models/asset');
const SettingsService = require('../../../services/settings');
const ActionModel = require('../../../models/action');
const CommentModel = require('../../../models/comment');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const UserModel = require('../../../../models/user');
const AssetModel = require('../../../../models/asset');
const SettingsService = require('../../../../services/settings');
const ActionModel = require('../../../../models/action');
const CommentModel = require('../../../../models/comment');
describe('graph.loaders.Metrics', () => {
beforeEach(() => SettingsService.init());
@@ -1,11 +1,11 @@
const expect = require('chai').expect;
const {graphql} = require('graphql');
const schema = require('../../../graph/schema');
const Context = require('../../../graph/context');
const UserModel = require('../../../models/user');
const SettingsService = require('../../../services/settings');
const CommentsService = require('../../../services/comments');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const UserModel = require('../../../../models/user');
const SettingsService = require('../../../../services/settings');
const CommentsService = require('../../../../services/comments');
describe('graph.mutations.addCommentTag', () => {
let comment;
@@ -1,12 +1,12 @@
const expect = require('chai').expect;
const {graphql} = require('graphql');
const schema = require('../../../graph/schema');
const Context = require('../../../graph/context');
const UserModel = require('../../../models/user');
const AssetModel = require('../../../models/asset');
const SettingsService = require('../../../services/settings');
const ActionModel = require('../../../models/action');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const UserModel = require('../../../../models/user');
const AssetModel = require('../../../../models/asset');
const SettingsService = require('../../../../services/settings');
const ActionModel = require('../../../../models/action');
describe('graph.mutations.createComment', () => {
beforeEach(() => SettingsService.init());
@@ -1,11 +1,11 @@
const expect = require('chai').expect;
const {graphql} = require('graphql');
const schema = require('../../../graph/schema');
const Context = require('../../../graph/context');
const UserModel = require('../../../models/user');
const SettingsService = require('../../../services/settings');
const CommentsService = require('../../../services/comments');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const UserModel = require('../../../../models/user');
const SettingsService = require('../../../../services/settings');
const CommentsService = require('../../../../services/comments');
describe('graph.mutations.removeCommentTag', () => {
let comment;
+1 -1
View File
@@ -1,4 +1,4 @@
const kue = require('../services/kue');
const kue = require('../../services/kue');
beforeEach(() => {
+1 -1
View File
@@ -1,4 +1,4 @@
const mongoose = require('./helpers/mongoose');
const mongoose = require('../helpers/mongoose');
before(function(done) {
this.timeout(30000);
+1 -1
View File
@@ -1,4 +1,4 @@
const authorization = require('../middleware/authorization');
const authorization = require('../../middleware/authorization');
// Add the passport middleware here before it's setup.
authorization.middleware.push((req, res, next) => {
@@ -1,17 +1,17 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const app = require('../../../../../app');
const chai = require('chai');
const expect = chai.expect;
const SettingsService = require('../../../../services/settings');
const SettingsService = require('../../../../../services/settings');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const UsersService = require('../../../../services/users');
const UsersService = require('../../../../../services/users');
describe('/api/v1/account/username', () => {
let mockUser;
@@ -1,6 +1,6 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const app = require('../../../../../app');
const chai = require('chai');
const expect = chai.expect;
@@ -8,9 +8,9 @@ const expect = chai.expect;
chai.should();
chai.use(require('chai-http'));
const AssetModel = require('../../../../models/asset');
const AssetsService = require('../../../../services/assets');
const SettingsService = require('../../../../services/settings');
const AssetModel = require('../../../../../models/asset');
const AssetsService = require('../../../../../services/assets');
const SettingsService = require('../../../../../services/settings');
describe('/api/v1/assets', () => {
@@ -1,10 +1,10 @@
const app = require('../../../../app');
const app = require('../../../../../app');
const chai = require('chai');
const expect = chai.expect;
chai.use(require('chai-http'));
const UsersService = require('../../../../services/users');
const UsersService = require('../../../../../services/users');
describe('/api/v1/auth', () => {
describe('#get', () => {
@@ -19,7 +19,7 @@ describe('/api/v1/auth', () => {
});
});
const SettingsService = require('../../../../services/settings');
const SettingsService = require('../../../../../services/settings');
describe('/api/v1/auth/local', () => {
@@ -1,13 +1,13 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const app = require('../../../../../app');
const chai = require('chai');
const expect = chai.expect;
chai.should();
chai.use(require('chai-http'));
const SettingsService = require('../../../../services/settings');
const SettingsService = require('../../../../../services/settings');
const defaults = {id: '1', moderation: 'PRE'};
describe('/api/v1/settings', () => {
@@ -1,18 +1,18 @@
const passport = require('../../../passport');
const app = require('../../../../app');
const mailer = require('../../../../services/mailer');
const app = require('../../../../../app');
const mailer = require('../../../../../services/mailer');
const chai = require('chai');
const expect = chai.expect;
const SettingsService = require('../../../../services/settings');
const SettingsService = require('../../../../../services/settings');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
// Setup chai.
chai.should();
chai.use(require('chai-http'));
const UsersService = require('../../../../services/users');
const UsersService = require('../../../../../services/users');
describe('/api/v1/users/:user_id/email/confirm', () => {
@@ -1,5 +1,5 @@
const ActionModel = require('../../models/action');
const ActionsService = require('../../services/actions');
const ActionModel = require('../../../models/action');
const ActionsService = require('../../../services/actions');
const expect = require('chai').expect;
@@ -1,6 +1,6 @@
const AssetModel = require('../../models/asset');
const AssetsService = require('../../services/assets');
const SettingsService = require('../../services/settings');
const AssetModel = require('../../../models/asset');
const AssetsService = require('../../../services/assets');
const SettingsService = require('../../../services/settings');
const chai = require('chai');
const expect = chai.expect;
@@ -1,10 +1,10 @@
const CommentModel = require('../../models/comment');
const ActionModel = require('../../models/action');
const CommentModel = require('../../../models/comment');
const ActionModel = require('../../../models/action');
const ActionsService = require('../../services/actions');
const UsersService = require('../../services/users');
const SettingsService = require('../../services/settings');
const CommentsService = require('../../services/comments');
const ActionsService = require('../../../services/actions');
const UsersService = require('../../../services/users');
const SettingsService = require('../../../services/settings');
const CommentsService = require('../../../services/comments');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
@@ -1,6 +1,6 @@
const expect = require('chai').expect;
const Domainlist = require('../../services/domainlist');
const SettingsService = require('../../services/settings');
const Domainlist = require('../../../services/domainlist');
const SettingsService = require('../../../services/settings');
describe('services.Domainlist', () => {
@@ -1,4 +1,4 @@
const SettingsService = require('../../services/settings');
const SettingsService = require('../../../services/settings');
const expect = require('chai').expect;
describe('services.SettingsService', () => {
@@ -1,5 +1,5 @@
const UsersService = require('../../services/users');
const SettingsService = require('../../services/settings');
const UsersService = require('../../../services/users');
const SettingsService = require('../../../services/settings');
const expect = require('chai').expect;
@@ -1,7 +1,7 @@
const expect = require('chai').expect;
const Errors = require('../../errors');
const Wordlist = require('../../services/wordlist');
const SettingsService = require('../../services/settings');
const Errors = require('../../../errors');
const Wordlist = require('../../../services/wordlist');
const SettingsService = require('../../../services/settings');
describe('services.Wordlist', () => {
+1
View File
@@ -4,6 +4,7 @@
<meta property="csrf" content="<%= csrfToken %>">
<link rel="stylesheet" type="text/css" href="/client/embed/stream/default.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<% if (locals.customCssUrl) { %>
<link href="<%= customCssUrl %>" rel="stylesheet" type="text/css">
<% } %>
+37 -1
View File
@@ -1,10 +1,30 @@
const path = require('path');
const fs = require('fs');
const autoprefixer = require('autoprefixer');
const precss = require('precss');
const Copy = require('copy-webpack-plugin');
const LicenseWebpackPlugin = require('license-webpack-plugin');
const webpack = require('webpack');
// Possibly load the config from the .env file (if there is one).
require('dotenv').config();
let pluginsConfigPath;
let envPlugins = path.join(__dirname, 'plugins.env.js');
let customPlugins = path.join(__dirname, 'plugins.json');
let defaultPlugins = path.join(__dirname, 'plugins.default.json');
if (process.env.TALK_PLUGINS_JSON && process.env.TALK_PLUGINS_JSON.length > 0) {
pluginsConfigPath = envPlugins;
} else if (fs.existsSync(customPlugins)) {
pluginsConfigPath = customPlugins;
} else {
pluginsConfigPath = defaultPlugins;
}
console.log(`Using ${pluginsConfigPath} as the plugin configuration path`);
// Edit the build targets and embeds below.
const buildTargets = [
@@ -53,6 +73,11 @@ module.exports = {
},
module: {
rules: [
{
loader: 'plugins-loader',
test: /\.(json|js)$/,
include: pluginsConfigPath
},
{
loader: 'babel-loader',
exclude: /node_modules/,
@@ -115,12 +140,23 @@ module.exports = {
}),
new webpack.DefinePlugin({
'process.env': {
'VERSION': `"${require('./package.json').version}"`
'VERSION': `"${require('./package.json').version}"`,
}
}),
new webpack.EnvironmentPlugin({
'TALK_PLUGINS_JSON': '{}'
})
],
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'client/coral-framework/loaders')],
},
resolve: {
alias: {
plugins: path.resolve(__dirname, 'plugins/'),
pluginsConfig: pluginsConfigPath
},
modules: [
path.resolve(__dirname, 'plugins'),
path.resolve(__dirname, 'client'),
...buildTargets.map(target => path.join(__dirname, 'client', target, 'src')),
...buildEmbeds.map(embed => path.join(__dirname, 'client', `coral-embed-${embed}`, 'src')),
+172 -23
View File
@@ -26,13 +26,17 @@
"@types/express-serve-static-core" "*"
"@types/serve-static" "*"
"@types/graphql@^0.8.0", "@types/graphql@^0.8.5", "@types/graphql@^0.8.6":
"@types/graphql@^0.8.5", "@types/graphql@^0.8.6":
version "0.8.6"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.8.6.tgz#b34fb880493ba835b0c067024ee70130d6f9bb68"
"@types/isomorphic-fetch@0.0.30":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.30.tgz#a21717624cde9a48c2db53a4e500fc5c32a99bbc"
"@types/graphql@^0.9.0":
version "0.9.0"
resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.9.0.tgz#fccf859f0d2817687f210737dc3be48a18b1d754"
"@types/isomorphic-fetch@0.0.33":
version "0.0.33"
resolved "https://registry.yarnpkg.com/@types/isomorphic-fetch/-/isomorphic-fetch-0.0.33.tgz#3ea1b86f8b73e6a7430d01d4dbd5b1f63fd72718"
"@types/mime@*":
version "0.0.29"
@@ -175,19 +179,20 @@ anymatch@^1.3.0:
arrify "^1.0.0"
micromatch "^2.1.5"
apollo-client@^0.8.3:
version "0.8.6"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-0.8.6.tgz#9aa18b03ec338f0a3804122df7f77493a10b72a0"
apollo-client@^1.0.0, apollo-client@^1.0.0-rc.9:
version "1.0.2"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.0.2.tgz#4355bd49d53a1489bc91d9f56d5b3d0ffe33fb3c"
dependencies:
graphql-anywhere "^2.1.0"
graphql-tag "^1.1.1"
graphql "^0.9.1"
graphql-anywhere "^3.0.1"
graphql-tag "^2.0.0"
redux "^3.4.0"
symbol-observable "^1.0.2"
whatwg-fetch "^2.0.0"
optionalDependencies:
"@types/async" "^2.0.31"
"@types/graphql" "^0.8.0"
"@types/isomorphic-fetch" "0.0.30"
"@types/graphql" "^0.9.0"
"@types/isomorphic-fetch" "0.0.33"
app-module-path@^2.2.0:
version "2.2.0"
@@ -360,6 +365,27 @@ aws4@^1.2.1:
version "1.5.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755"
babel-cli@^6.24.0:
version "6.24.0"
resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.24.0.tgz#a05ffd210dca0c288a26d5319c5ac8669a265ad0"
dependencies:
babel-core "^6.24.0"
babel-polyfill "^6.23.0"
babel-register "^6.24.0"
babel-runtime "^6.22.0"
commander "^2.8.1"
convert-source-map "^1.1.0"
fs-readdir-recursive "^1.0.0"
glob "^7.0.0"
lodash "^4.2.0"
output-file-sync "^1.1.0"
path-is-absolute "^1.0.0"
slash "^1.0.0"
source-map "^0.5.0"
v8flags "^2.0.10"
optionalDependencies:
chokidar "^1.6.1"
babel-code-frame@^6.11.0, babel-code-frame@^6.16.0, babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
@@ -622,11 +648,15 @@ babel-plugin-syntax-export-extensions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721"
babel-plugin-syntax-flow@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d"
babel-plugin-syntax-function-bind@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46"
babel-plugin-syntax-jsx@^6.8.0:
babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946"
@@ -879,6 +909,13 @@ babel-plugin-transform-export-extensions@^6.22.0:
babel-plugin-syntax-export-extensions "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-flow-strip-types@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf"
dependencies:
babel-plugin-syntax-flow "^6.18.0"
babel-runtime "^6.22.0"
babel-plugin-transform-function-bind@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97"
@@ -899,6 +936,26 @@ babel-plugin-transform-object-rest-spread@^6.22.0, babel-plugin-transform-object
babel-plugin-syntax-object-rest-spread "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-react-display-name@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.23.0.tgz#4398910c358441dc4cef18787264d0412ed36b37"
dependencies:
babel-runtime "^6.22.0"
babel-plugin-transform-react-jsx-self@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e"
dependencies:
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-react-jsx-source@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6"
dependencies:
babel-plugin-syntax-jsx "^6.8.0"
babel-runtime "^6.22.0"
babel-plugin-transform-react-jsx@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.23.0.tgz#23e892f7f2e759678eb5e4446a8f8e94e81b3470"
@@ -957,12 +1014,29 @@ babel-preset-es2015@^6.24.0:
babel-plugin-transform-es2015-unicode-regex "^6.22.0"
babel-plugin-transform-regenerator "^6.22.0"
babel-preset-flow@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d"
dependencies:
babel-plugin-transform-flow-strip-types "^6.22.0"
babel-preset-jest@^19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-19.0.0.tgz#22d67201d02324a195811288eb38294bb3cac396"
dependencies:
babel-plugin-jest-hoist "^19.0.0"
babel-preset-react@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.23.0.tgz#eb7cee4de98a3f94502c28565332da9819455195"
dependencies:
babel-plugin-syntax-jsx "^6.3.13"
babel-plugin-transform-react-display-name "^6.23.0"
babel-plugin-transform-react-jsx "^6.23.0"
babel-plugin-transform-react-jsx-self "^6.22.0"
babel-plugin-transform-react-jsx-source "^6.22.0"
babel-preset-flow "^6.23.0"
babel-preset-stage-0@^6.16.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.22.0.tgz#707eeb5b415da769eff9c42f4547f644f9296ef9"
@@ -1483,7 +1557,7 @@ cheerio@^0.22.0:
lodash.reject "^4.4.0"
lodash.some "^4.4.0"
chokidar@^1.4.3:
chokidar@^1.4.3, chokidar@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2"
dependencies:
@@ -1683,7 +1757,7 @@ commander@2.8.x:
dependencies:
graceful-readlink ">= 1.0.0"
commander@2.9.0, commander@^2.5.0, commander@^2.9.0:
commander@2.9.0, commander@^2.5.0, commander@^2.8.1, commander@^2.9.0:
version "2.9.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4"
dependencies:
@@ -1693,6 +1767,12 @@ commander@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781"
common-tags@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0"
dependencies:
babel-runtime "^6.18.0"
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -3079,6 +3159,10 @@ fs-promise@^0.3.1:
dependencies:
any-promise "~0.1.0"
fs-readdir-recursive@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@@ -3372,7 +3456,7 @@ gql-utils@^0.0.2:
bluebird "^3.4.6"
glob "^7.1.1"
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
@@ -3384,9 +3468,9 @@ graceful-fs@~2.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
graphql-anywhere@^2.0.0, graphql-anywhere@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-2.1.0.tgz#888c0a1718db3ff866b313070747777380560f69"
graphql-anywhere@^3.0.0, graphql-anywhere@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.0.1.tgz#73531db861174c8f212eafb9f8e84944b38b4e5a"
graphql-docs@^0.2.0:
version "0.2.0"
@@ -3425,10 +3509,18 @@ graphql-server-module-graphiql@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/graphql-server-module-graphiql/-/graphql-server-module-graphiql-0.5.2.tgz#7e2a0c78b0267e784f8483ce5633810baf558dee"
graphql-tag@^1.1.1, graphql-tag@^1.2.3:
graphql-tag@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.2.3.tgz#74c62443fbf3e693647426d7359f7e3e6ce7dace"
graphql-tag@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-1.3.2.tgz#7abb3a8fd9f3415d07163314ed237061c785b759"
graphql-tag@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.0.0.tgz#f3efe3b4d64f33bfe8479ae06a461c9d72f2a6fe"
graphql-tools@^0.9.0:
version "0.9.2"
resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-0.9.2.tgz#c3f42d0b78d2d6c57cea5ef2894863de34af9a11"
@@ -3451,6 +3543,12 @@ graphql@^0.8.2:
dependencies:
iterall "1.0.2"
graphql@^0.9.1:
version "0.9.2"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.9.2.tgz#2cb5c635de13f790a77c5879649cb401b1589386"
dependencies:
iterall "1.0.3"
growl@1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.8.1.tgz#4b2dec8d907e93db336624dcec0183502f8c9428"
@@ -3567,6 +3665,10 @@ hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
hoek@4.x.x:
version "4.1.1"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.1.1.tgz#9cc573ffba2b7b408fb5e9c2a13796be94cddce9"
hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
@@ -4097,6 +4199,10 @@ isemail@1.x.x:
version "1.2.0"
resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a"
isemail@2.x.x:
version "2.2.1"
resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6"
isexe@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0"
@@ -4195,10 +4301,18 @@ istanbul@^1.1.0-alpha.1:
which "^1.1.1"
wordwrap "^1.0.0"
items@2.x.x:
version "2.1.1"
resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198"
iterall@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.2.tgz#41a2e96ce9eda5e61c767ee5dc312373bb046e91"
iterall@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.3.tgz#e0b31958f835013c323ff0b10943829ac69aa4b7"
jade@0.26.3:
version "0.26.3"
resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c"
@@ -4212,6 +4326,15 @@ jodid25519@^1.0.0:
dependencies:
jsbn "~0.1.0"
joi@^10.4.1:
version "10.4.1"
resolved "https://registry.yarnpkg.com/joi/-/joi-10.4.1.tgz#a2fca1f0d603d1b843f2c1e086b52461f6be1f36"
dependencies:
hoek "4.x.x"
isemail "2.x.x"
items "2.x.x"
topo "2.x.x"
joi@^6.10.1:
version "6.10.1"
resolved "https://registry.yarnpkg.com/joi/-/joi-6.10.1.tgz#4d50c318079122000fe5f16af1ff8e1917b77e06"
@@ -5496,6 +5619,14 @@ osenv@^0.1.0:
os-homedir "^1.0.0"
os-tmpdir "^1.0.0"
output-file-sync@^1.1.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76"
dependencies:
graceful-fs "^4.1.4"
mkdirp "^0.5.1"
object-assign "^4.1.0"
p-limit@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
@@ -6482,11 +6613,13 @@ react-addons-test-utils@^15.4.2:
fbjs "^0.8.4"
object-assign "^4.1.0"
react-apollo@^0.10.0:
version "0.10.1"
resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-0.10.1.tgz#97fd50855f8575672aa68330b9c64a201cd13343"
react-apollo@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-1.0.0.tgz#7fcc14adcc7aa4ca4d9e04ddedf50b8fb74daa91"
dependencies:
graphql-anywhere "^2.0.0"
apollo-client "^1.0.0-rc.9"
graphql-anywhere "^3.0.0"
graphql-tag "^1.3.1"
hoist-non-react-statics "^1.2.0"
invariant "^2.2.1"
lodash.flatten "^4.2.0"
@@ -7658,6 +7791,12 @@ topo@1.x.x:
dependencies:
hoek "2.x.x"
topo@2.x.x:
version "2.0.2"
resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182"
dependencies:
hoek "4.x.x"
touch@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/touch/-/touch-1.0.0.tgz#449cbe2dbae5a8c8038e30d71fa0ff464947c4de"
@@ -7816,6 +7955,10 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
user-home@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190"
user-home@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f"
@@ -7844,6 +7987,12 @@ uuid@^3.0.0, uuid@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"
v8flags@^2.0.10:
version "2.0.11"
resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.0.11.tgz#bca8f30f0d6d60612cc2c00641e6962d42ae6881"
dependencies:
user-home "^1.1.1"
validate-commit-msg@2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/validate-commit-msg/-/validate-commit-msg-2.5.0.tgz#a7d0a68aa3917171b560664689a2ac59c25908b6"