mirror of
https://github.com/wassname/talk.git
synced 2026-09-10 12:43:11 +08:00
Merge branch 'master' into i18n-refactor
This commit is contained in:
@@ -6,14 +6,14 @@ import I18n from 'coral-i18n/modules/i18n/i18n';
|
||||
const lang = new I18n();
|
||||
|
||||
export const fetchAssetRequest = () => ({type: actions.FETCH_ASSET_REQUEST});
|
||||
export const fetchAssetSuccess = asset => ({type: actions.FETCH_ASSET_SUCCESS, asset});
|
||||
export const fetchAssetFailure = error => ({type: actions.FETCH_ASSET_FAILURE, error});
|
||||
export const fetchAssetSuccess = (asset) => ({type: actions.FETCH_ASSET_SUCCESS, asset});
|
||||
export const fetchAssetFailure = (error) => ({type: actions.FETCH_ASSET_FAILURE, error});
|
||||
|
||||
const updateAssetSettingsRequest = () => ({type: actions.UPDATE_ASSET_SETTINGS_REQUEST});
|
||||
const updateAssetSettingsSuccess = settings => ({type: actions.UPDATE_ASSET_SETTINGS_SUCCESS, settings});
|
||||
const updateAssetSettingsSuccess = (settings) => ({type: actions.UPDATE_ASSET_SETTINGS_SUCCESS, settings});
|
||||
const updateAssetSettingsFailure = () => ({type: actions.UPDATE_ASSET_SETTINGS_FAILURE});
|
||||
|
||||
export const updateConfiguration = newConfig => (dispatch, getState) => {
|
||||
export const updateConfiguration = (newConfig) => (dispatch, getState) => {
|
||||
const assetId = getState().asset.toJS().id;
|
||||
dispatch(updateAssetSettingsRequest());
|
||||
coralApi(`/assets/${assetId}/settings`, {method: 'PUT', body: newConfig})
|
||||
@@ -21,10 +21,10 @@ export const updateConfiguration = newConfig => (dispatch, getState) => {
|
||||
dispatch(addNotification('success', lang.t('framework.success_update_settings')));
|
||||
dispatch(updateAssetSettingsSuccess(newConfig));
|
||||
})
|
||||
.catch(error => dispatch(updateAssetSettingsFailure(error)));
|
||||
.catch((error) => dispatch(updateAssetSettingsFailure(error)));
|
||||
};
|
||||
|
||||
export const updateOpenStream = closedBody => (dispatch, getState) => {
|
||||
export const updateOpenStream = (closedBody) => (dispatch, getState) => {
|
||||
const assetId = getState().asset.toJS().id;
|
||||
dispatch(fetchAssetRequest());
|
||||
coralApi(`/assets/${assetId}/status`, {method: 'PUT', body: closedBody})
|
||||
@@ -32,13 +32,13 @@ export const updateOpenStream = closedBody => (dispatch, getState) => {
|
||||
dispatch(addNotification('success', lang.t('framework.success_update_settings')));
|
||||
dispatch(fetchAssetSuccess(closedBody));
|
||||
})
|
||||
.catch(error => dispatch(fetchAssetFailure(error)));
|
||||
.catch((error) => dispatch(fetchAssetFailure(error)));
|
||||
};
|
||||
|
||||
const openStream = () => ({type: actions.OPEN_COMMENTS});
|
||||
const closeStream = () => ({type: actions.CLOSE_COMMENTS});
|
||||
|
||||
export const updateOpenStatus = status => dispatch => {
|
||||
export const updateOpenStatus = (status) => (dispatch) => {
|
||||
if (status === 'open') {
|
||||
dispatch(openStream());
|
||||
dispatch(updateOpenStream({closedAt: null}));
|
||||
|
||||
@@ -1,37 +1,15 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import client from 'coral-framework/services/client';
|
||||
import I18n from '../../coral-i18n/modules/i18n/i18n';
|
||||
const lang = new I18n();
|
||||
import {pym} from 'coral-framework';
|
||||
import * as Storage from '../helpers/storage';
|
||||
import * as actions from '../constants/auth';
|
||||
import coralApi, {base} from '../helpers/response';
|
||||
import {pym} from 'coral-framework';
|
||||
import jwtDecode from 'jwt-decode';
|
||||
|
||||
const ME_QUERY = gql`
|
||||
query Me {
|
||||
me {
|
||||
status
|
||||
comments {
|
||||
id
|
||||
body
|
||||
asset {
|
||||
id
|
||||
title
|
||||
url
|
||||
}
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function fetchMe() {
|
||||
return client.query({
|
||||
fetchPolicy: 'network-only',
|
||||
query: ME_QUERY});
|
||||
}
|
||||
const lang = new I18n(translations);
|
||||
import translations from './../translations';
|
||||
import I18n from '../../coral-framework/modules/i18n/i18n';
|
||||
|
||||
// Dialog Actions
|
||||
export const showSignInDialog = () => dispatch => {
|
||||
export const showSignInDialog = () => (dispatch) => {
|
||||
const signInPopUp = window.open(
|
||||
'/embed/stream/login',
|
||||
'Login',
|
||||
@@ -49,27 +27,41 @@ export const showSignInDialog = () => dispatch => {
|
||||
signInPopUp.onunload = () => {
|
||||
if (loaded) {
|
||||
dispatch(checkLogin());
|
||||
fetchMe();
|
||||
}
|
||||
};
|
||||
|
||||
dispatch({type: actions.SHOW_SIGNIN_DIALOG});
|
||||
};
|
||||
export const hideSignInDialog = () => dispatch => {
|
||||
export const hideSignInDialog = () => (dispatch) => {
|
||||
dispatch({type: actions.HIDE_SIGNIN_DIALOG});
|
||||
window.close();
|
||||
};
|
||||
|
||||
export const createUsernameRequest = () => ({type: actions.CREATE_USERNAME_REQUEST});
|
||||
export const showCreateUsernameDialog = () => ({type: actions.SHOW_CREATEUSERNAME_DIALOG});
|
||||
export const hideCreateUsernameDialog = () => ({type: actions.HIDE_CREATEUSERNAME_DIALOG});
|
||||
export const createUsernameRequest = () => ({
|
||||
type: actions.CREATE_USERNAME_REQUEST
|
||||
});
|
||||
export const showCreateUsernameDialog = () => ({
|
||||
type: actions.SHOW_CREATEUSERNAME_DIALOG
|
||||
});
|
||||
export const hideCreateUsernameDialog = () => ({
|
||||
type: actions.HIDE_CREATEUSERNAME_DIALOG
|
||||
});
|
||||
|
||||
const createUsernameSuccess = () => ({type: actions.CREATE_USERNAME_SUCCESS});
|
||||
const createUsernameFailure = error => ({type: actions.CREATE_USERNAME_FAILURE, error});
|
||||
const createUsernameSuccess = () => ({
|
||||
type: actions.CREATE_USERNAME_SUCCESS
|
||||
});
|
||||
|
||||
export const updateUsername = ({username}) => ({type: actions.UPDATE_USERNAME, username});
|
||||
const createUsernameFailure = (error) => ({
|
||||
type: actions.CREATE_USERNAME_FAILURE,
|
||||
error
|
||||
});
|
||||
|
||||
export const createUsername = (userId, formData) => dispatch => {
|
||||
export const updateUsername = ({username}) => ({
|
||||
type: actions.UPDATE_USERNAME,
|
||||
username
|
||||
});
|
||||
|
||||
export const createUsername = (userId, formData) => (dispatch) => {
|
||||
dispatch(createUsernameRequest());
|
||||
coralApi('/account/username', {method: 'PUT', body: formData})
|
||||
.then(() => {
|
||||
@@ -77,18 +69,18 @@ export const createUsername = (userId, formData) => dispatch => {
|
||||
dispatch(hideCreateUsernameDialog());
|
||||
dispatch(updateUsername(formData));
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
dispatch(createUsernameFailure(lang.t(`error.${error.translation_key}`)));
|
||||
});
|
||||
};
|
||||
|
||||
export const changeView = view => dispatch => {
|
||||
export const changeView = (view) => (dispatch) => {
|
||||
dispatch({
|
||||
type: actions.CHANGE_VIEW,
|
||||
view
|
||||
});
|
||||
|
||||
switch(view) {
|
||||
switch (view) {
|
||||
case 'SIGNUP':
|
||||
window.resizeTo(500, 800);
|
||||
break;
|
||||
@@ -100,26 +92,49 @@ export const changeView = view => dispatch => {
|
||||
}
|
||||
};
|
||||
|
||||
export const cleanState = () => ({type: actions.CLEAN_STATE});
|
||||
export const cleanState = () => ({
|
||||
type: actions.CLEAN_STATE
|
||||
});
|
||||
|
||||
// Sign In Actions
|
||||
|
||||
const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST});
|
||||
const signInRequest = () => ({
|
||||
type: actions.FETCH_SIGNIN_REQUEST
|
||||
});
|
||||
|
||||
// TODO: revisit login redux flow.
|
||||
// const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin});
|
||||
//
|
||||
const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
|
||||
const signInFailure = (error) => ({
|
||||
type: actions.FETCH_SIGNIN_FAILURE,
|
||||
error
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// AUTH TOKEN
|
||||
//==============================================================================
|
||||
|
||||
export const handleAuthToken = (token) => (dispatch) => {
|
||||
Storage.setItem('exp', jwtDecode(token).exp);
|
||||
Storage.setItem('token', token);
|
||||
dispatch({type: 'HANDLE_AUTH_TOKEN'});
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// SIGN IN
|
||||
//==============================================================================
|
||||
|
||||
export const fetchSignIn = (formData) => (dispatch) => {
|
||||
dispatch(signInRequest());
|
||||
return coralApi('/auth/local', {method: 'POST', body: formData})
|
||||
.then(() => dispatch(hideSignInDialog()))
|
||||
.catch(error => {
|
||||
.then(({token}) => {
|
||||
dispatch(handleAuthToken(token));
|
||||
dispatch(hideSignInDialog());
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.metadata) {
|
||||
|
||||
// the user might not have a valid email. prompt the user user re-request the confirmation email
|
||||
dispatch(signInFailure(lang.t('error.email_not_verified', error.metadata)));
|
||||
dispatch(
|
||||
signInFailure(lang.t('error.email_not_verified', error.metadata))
|
||||
);
|
||||
} else {
|
||||
|
||||
// invalid credentials
|
||||
@@ -128,13 +143,23 @@ export const fetchSignIn = (formData) => (dispatch) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Sign In - Facebook
|
||||
//==============================================================================
|
||||
// SIGN IN - FACEBOOK
|
||||
//==============================================================================
|
||||
|
||||
const signInFacebookRequest = () => ({type: actions.FETCH_SIGNIN_FACEBOOK_REQUEST});
|
||||
const signInFacebookSuccess = user => ({type: actions.FETCH_SIGNIN_FACEBOOK_SUCCESS, user});
|
||||
const signInFacebookFailure = error => ({type: actions.FETCH_SIGNIN_FACEBOOK_FAILURE, error});
|
||||
const signInFacebookRequest = () => ({
|
||||
type: actions.FETCH_SIGNIN_FACEBOOK_REQUEST
|
||||
});
|
||||
const signInFacebookSuccess = (user) => ({
|
||||
type: actions.FETCH_SIGNIN_FACEBOOK_SUCCESS,
|
||||
user
|
||||
});
|
||||
const signInFacebookFailure = (error) => ({
|
||||
type: actions.FETCH_SIGNIN_FACEBOOK_FAILURE,
|
||||
error
|
||||
});
|
||||
|
||||
export const fetchSignInFacebook = () => dispatch => {
|
||||
export const fetchSignInFacebook = () => (dispatch) => {
|
||||
dispatch(signInFacebookRequest());
|
||||
window.open(
|
||||
`${base}/auth/facebook`,
|
||||
@@ -143,11 +168,15 @@ export const fetchSignInFacebook = () => dispatch => {
|
||||
);
|
||||
};
|
||||
|
||||
// Sign Up Facebook
|
||||
//==============================================================================
|
||||
// SIGN UP - FACEBOOK
|
||||
//==============================================================================
|
||||
|
||||
const signUpFacebookRequest = () => ({type: actions.FETCH_SIGNUP_FACEBOOK_REQUEST});
|
||||
const signUpFacebookRequest = () => ({
|
||||
type: actions.FETCH_SIGNUP_FACEBOOK_REQUEST
|
||||
});
|
||||
|
||||
export const fetchSignUpFacebook = () => dispatch => {
|
||||
export const fetchSignUpFacebook = () => (dispatch) => {
|
||||
dispatch(signUpFacebookRequest());
|
||||
window.open(
|
||||
`${base}/auth/facebook`,
|
||||
@@ -156,7 +185,7 @@ export const fetchSignUpFacebook = () => dispatch => {
|
||||
);
|
||||
};
|
||||
|
||||
export const facebookCallback = (err, data) => dispatch => {
|
||||
export const facebookCallback = (err, data) => (dispatch) => {
|
||||
if (err) {
|
||||
dispatch(signInFacebookFailure(err));
|
||||
return;
|
||||
@@ -173,20 +202,26 @@ export const facebookCallback = (err, data) => dispatch => {
|
||||
}
|
||||
};
|
||||
|
||||
// Sign Up Actions
|
||||
//==============================================================================
|
||||
// SIGN UP
|
||||
//==============================================================================
|
||||
|
||||
const signUpRequest = () => ({type: actions.FETCH_SIGNUP_REQUEST});
|
||||
const signUpSuccess = user => ({type: actions.FETCH_SIGNUP_SUCCESS, user});
|
||||
const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
|
||||
const signUpSuccess = (user) => ({type: actions.FETCH_SIGNUP_SUCCESS, user});
|
||||
const signUpFailure = (error) => ({type: actions.FETCH_SIGNUP_FAILURE, error});
|
||||
|
||||
export const fetchSignUp = (formData, redirectUri) => (dispatch) => {
|
||||
dispatch(signUpRequest());
|
||||
|
||||
coralApi('/users', {method: 'POST', body: formData, headers: {'X-Pym-Url': redirectUri}})
|
||||
coralApi('/users', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {'X-Pym-Url': redirectUri}
|
||||
})
|
||||
.then(({user}) => {
|
||||
dispatch(signUpSuccess(user));
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
let errorMessage = lang.t(`error.${error.message}`);
|
||||
|
||||
// if there is no translation defined, just show the error string
|
||||
@@ -197,75 +232,105 @@ export const fetchSignUp = (formData, redirectUri) => (dispatch) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Forgot Password Actions
|
||||
//==============================================================================
|
||||
// FORGOT PASSWORD
|
||||
//==============================================================================
|
||||
|
||||
const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUEST});
|
||||
const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS});
|
||||
const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE});
|
||||
const forgotPasswordRequest = () => ({
|
||||
type: actions.FETCH_FORGOT_PASSWORD_REQUEST
|
||||
});
|
||||
|
||||
export const fetchForgotPassword = email => (dispatch) => {
|
||||
dispatch(forgotPassowordRequest(email));
|
||||
const forgotPasswordSuccess = () => ({
|
||||
type: actions.FETCH_FORGOT_PASSWORD_SUCCESS
|
||||
});
|
||||
|
||||
const forgotPasswordFailure = () => ({
|
||||
type: actions.FETCH_FORGOT_PASSWORD_FAILURE
|
||||
});
|
||||
|
||||
export const fetchForgotPassword = (email) => (dispatch) => {
|
||||
dispatch(forgotPasswordRequest(email));
|
||||
const redirectUri = pym.parentUrl || location.href;
|
||||
coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}})
|
||||
.then(() => dispatch(forgotPassowordSuccess()))
|
||||
.catch(error => dispatch(forgotPassowordFailure(error)));
|
||||
coralApi('/account/password/reset', {
|
||||
method: 'POST',
|
||||
body: {email, loc: redirectUri}
|
||||
})
|
||||
.then(() => dispatch(forgotPasswordSuccess()))
|
||||
.catch((error) => dispatch(forgotPasswordFailure(error)));
|
||||
};
|
||||
|
||||
// LogOut Actions
|
||||
//==============================================================================
|
||||
// LOGOUT
|
||||
//==============================================================================
|
||||
|
||||
const logOutRequest = () => ({type: actions.LOGOUT_REQUEST});
|
||||
const logOutSuccess = () => ({type: actions.LOGOUT_SUCCESS});
|
||||
const logOutFailure = () => ({type: actions.LOGOUT_FAILURE});
|
||||
|
||||
export const logout = () => dispatch => {
|
||||
dispatch(logOutRequest());
|
||||
return coralApi('/auth', {method: 'DELETE'})
|
||||
.then(() => {
|
||||
dispatch(logOutSuccess());
|
||||
fetchMe();
|
||||
})
|
||||
.catch(error => dispatch(logOutFailure(error)));
|
||||
export const logout = () => (dispatch) => {
|
||||
return coralApi('/auth', {method: 'DELETE'}).then(() => {
|
||||
dispatch({type: actions.LOGOUT});
|
||||
Storage.removeItem('token');
|
||||
});
|
||||
};
|
||||
|
||||
// LogOut Actions
|
||||
|
||||
export const validForm = () => ({type: actions.VALID_FORM});
|
||||
export const invalidForm = error => ({type: actions.INVALID_FORM, error});
|
||||
|
||||
// Check Login
|
||||
//==============================================================================
|
||||
// CHECK LOGIN
|
||||
//==============================================================================
|
||||
|
||||
const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST});
|
||||
const checkLoginSuccess = (user, isAdmin) => ({type: actions.CHECK_LOGIN_SUCCESS, user, isAdmin});
|
||||
const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
const checkLoginFailure = (error) => ({type: actions.CHECK_LOGIN_FAILURE, error});
|
||||
|
||||
export const checkLogin = () => dispatch => {
|
||||
const checkLoginSuccess = (user, isAdmin) => ({
|
||||
type: actions.CHECK_LOGIN_SUCCESS,
|
||||
user,
|
||||
isAdmin
|
||||
});
|
||||
|
||||
export const checkLogin = () => (dispatch) => {
|
||||
dispatch(checkLoginRequest());
|
||||
coralApi('/auth')
|
||||
.then((result) => {
|
||||
if (!result.user) {
|
||||
Storage.removeItem('token');
|
||||
throw new Error('Not logged in');
|
||||
}
|
||||
|
||||
const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length;
|
||||
const isAdmin = !!result.user.roles.filter((i) => i === 'ADMIN').length;
|
||||
dispatch(checkLoginSuccess(result.user, isAdmin));
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
dispatch(checkLoginFailure(`${error.translation_key}`));
|
||||
});
|
||||
};
|
||||
|
||||
const verifyEmailRequest = () => ({type: actions.VERIFY_EMAIL_REQUEST});
|
||||
const verifyEmailSuccess = () => ({type: actions.VERIFY_EMAIL_SUCCESS});
|
||||
const verifyEmailFailure = () => ({type: actions.VERIFY_EMAIL_FAILURE});
|
||||
export const validForm = () => ({type: actions.VALID_FORM});
|
||||
export const invalidForm = (error) => ({type: actions.INVALID_FORM, error});
|
||||
|
||||
export const requestConfirmEmail = (email, redirectUri) => dispatch => {
|
||||
//==============================================================================
|
||||
// VERIFY EMAIL
|
||||
//==============================================================================
|
||||
|
||||
const verifyEmailRequest = () => ({
|
||||
type: actions.VERIFY_EMAIL_REQUEST
|
||||
});
|
||||
|
||||
const verifyEmailSuccess = () => ({
|
||||
type: actions.VERIFY_EMAIL_SUCCESS
|
||||
});
|
||||
|
||||
const verifyEmailFailure = () => ({
|
||||
type: actions.VERIFY_EMAIL_FAILURE
|
||||
});
|
||||
|
||||
export const requestConfirmEmail = (email, redirectUri) => (dispatch) => {
|
||||
dispatch(verifyEmailRequest());
|
||||
return coralApi('/users/resend-verify', {method: 'POST', body: {email}, headers: {'X-Pym-Url': redirectUri}})
|
||||
return coralApi('/users/resend-verify', {
|
||||
method: 'POST',
|
||||
body: {email},
|
||||
headers: {'X-Pym-Url': redirectUri}
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(verifyEmailSuccess());
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
|
||||
// email might have already been verifyed
|
||||
dispatch(verifyEmailFailure(err));
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as actions from '../constants/auth';
|
||||
import I18n from 'coral-i18n/modules/i18n/i18n';
|
||||
const lang = new I18n();
|
||||
|
||||
const editUsernameFailure = error => ({type: actions.EDIT_USERNAME_FAILURE, error});
|
||||
const editUsernameFailure = (error) => ({type: actions.EDIT_USERNAME_FAILURE, error});
|
||||
const editUsernameSuccess = () => ({type: actions.EDIT_USERNAME_SUCCESS});
|
||||
|
||||
export const editName = (username) => (dispatch) => {
|
||||
@@ -14,7 +14,7 @@ export const editName = (username) => (dispatch) => {
|
||||
dispatch(editUsernameSuccess());
|
||||
dispatch(addNotification('success', lang.t('framework.success_name_update')));
|
||||
})
|
||||
.catch(error => {
|
||||
.catch((error) => {
|
||||
dispatch(editUsernameFailure(lang.t(`error.${error.translation_key}`)));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.inline {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.debug {
|
||||
background-color: coral;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './Slot.css';
|
||||
import {connect} from 'react-redux';
|
||||
import {getSlotElements} from 'coral-framework/helpers/plugins';
|
||||
|
||||
export default function Slot ({fill, inline = false, ...rest}) {
|
||||
function Slot ({fill, inline = false, plugin_config: config, ...rest}) {
|
||||
return (
|
||||
<div className={cn({[styles.inline]: inline})}>
|
||||
{getSlotElements(fill, rest)}
|
||||
<div className={cn({[styles.inline]: inline, [styles.debug]: config.debug})}>
|
||||
{getSlotElements(fill, {...rest, config})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -14,3 +15,8 @@ export default function Slot ({fill, inline = false, ...rest}) {
|
||||
Slot.propTypes = {
|
||||
fill: React.PropTypes.string
|
||||
};
|
||||
|
||||
const mapStateToProps = ({config: {plugin_config = {}}}) => ({plugin_config});
|
||||
|
||||
export default connect(mapStateToProps, null)(Slot);
|
||||
|
||||
|
||||
@@ -8,4 +8,3 @@ export const UPDATE_ASSET_SETTINGS_FAILURE = 'UPDATE_ASSET_SETTINGS_FAILURE';
|
||||
|
||||
export const OPEN_COMMENTS = 'OPEN_COMMENTS';
|
||||
export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
|
||||
|
||||
|
||||
@@ -33,9 +33,7 @@ export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST';
|
||||
export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS';
|
||||
export const FETCH_FORGOT_PASSWORD_FAILURE = 'FETCH_FORGOT_PASSWORD_FAILURE';
|
||||
|
||||
export const LOGOUT_REQUEST = 'LOGOUT_REQUEST';
|
||||
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
|
||||
export const LOGOUT_FAILURE = 'LOGOUT_FAILURE';
|
||||
export const LOGOUT = 'LOGOUT';
|
||||
|
||||
export const INVALID_FORM = 'INVALID_FORM';
|
||||
export const VALID_FORM = 'VALID_FORM';
|
||||
@@ -44,8 +42,6 @@ export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST';
|
||||
export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS';
|
||||
export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
|
||||
|
||||
export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
|
||||
|
||||
export const VERIFY_EMAIL_REQUEST = 'VERIFY_EMAIL_REQUEST';
|
||||
export const VERIFY_EMAIL_SUCCESS = 'VERIFY_EMAIL_SUCCESS';
|
||||
export const VERIFY_EMAIL_FAILURE = 'VERIFY_EMAIL_FAILURE';
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
export const UPDATE_CONFIG_REQUEST = 'UPDATE_CONFIG_REQUEST';
|
||||
export const UPDATE_CONFIG_SUCCESS = 'UPDATE_CONFIG_SUCCESS';
|
||||
export const UPDATE_CONFIG_FAILURE = 'UPDATE_CONFIG_FAILURE';
|
||||
|
||||
export const UPDATE_CONFIG = 'UPDATE_CONFIG';
|
||||
|
||||
export const OPEN_COMMENTS = 'OPEN_COMMENTS';
|
||||
export const CLOSE_COMMENTS = 'CLOSE_COMMENTS';
|
||||
export const ADD_ITEM = 'ADD_ITEM';
|
||||
@@ -0,0 +1,2 @@
|
||||
// fragments defined here are automatically registered.
|
||||
export default {};
|
||||
@@ -1,8 +0,0 @@
|
||||
fragment actionSummaryView on ActionSummary {
|
||||
__typename
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
created_at
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#import "../fragments/actionSummaryView.graphql"
|
||||
|
||||
fragment commentView on Comment {
|
||||
id
|
||||
body
|
||||
created_at
|
||||
status
|
||||
tags {
|
||||
name
|
||||
}
|
||||
user {
|
||||
id
|
||||
name: username
|
||||
}
|
||||
action_summaries {
|
||||
...actionSummaryView
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import withMutation from '../hocs/withMutation';
|
||||
|
||||
export const withPostComment = withMutation(
|
||||
gql`
|
||||
mutation PostComment($comment: CreateCommentInput!) {
|
||||
createComment(comment: $comment) {
|
||||
...CreateCommentResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
postComment: (comment) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
comment
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const withEditComment = withMutation(
|
||||
gql`
|
||||
mutation EditComment($id: ID!, $asset_id: ID!, $edit: EditCommentInput) {
|
||||
editComment(id:$id, asset_id:$asset_id, edit:$edit) {
|
||||
...EditCommentResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
editComment: (id, asset_id, edit) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
asset_id,
|
||||
edit,
|
||||
},
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const withPostFlag = withMutation(
|
||||
gql`
|
||||
mutation PostFlag($flag: CreateFlagInput!) {
|
||||
createFlag(flag: $flag) {
|
||||
...CreateFlagResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
postFlag: (flag) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
flag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withPostDontAgree = withMutation(
|
||||
gql`
|
||||
mutation CreateDontAgree($dontagree: CreateDontAgreeInput!) {
|
||||
createDontAgree(dontagree: $dontagree) {
|
||||
...CreateDontAgreeResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
postDontAgree: (dontagree) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
dontagree
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withDeleteAction = withMutation(
|
||||
gql`
|
||||
mutation DeleteAction($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
...DeleteActionResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
deleteAction: (id) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withAddCommentTag = withMutation(
|
||||
gql`
|
||||
mutation AddCommentTag($id: ID!, $tag: String!) {
|
||||
addCommentTag(id:$id, tag:$tag) {
|
||||
...AddCommentTagResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
addCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withRemoveCommentTag = withMutation(
|
||||
gql`
|
||||
mutation RemoveCommentTag($id: ID!, $tag: String!) {
|
||||
removeCommentTag(id:$id, tag:$tag) {
|
||||
...RemoveCommentTagResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
removeCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withIgnoreUser = withMutation(
|
||||
gql`
|
||||
mutation IgnoreUser($id: ID!) {
|
||||
ignoreUser(id:$id) {
|
||||
...IgnoreUserResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
ignoreUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const withStopIgnoringUser = withMutation(
|
||||
gql`
|
||||
mutation StopIgnoringUser($id: ID!) {
|
||||
stopIgnoringUser(id:$id) {
|
||||
...StopIgnoringUserResponse
|
||||
}
|
||||
}
|
||||
`, {
|
||||
props: ({mutate}) => ({
|
||||
stopIgnoringUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
mutation AddCommentTag ($id: ID!, $tag: String!) {
|
||||
addCommentTag(id:$id, tag:$tag) {
|
||||
comment {
|
||||
id
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
mutation deleteAction ($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
mutation ignoreUser ($id: ID!) {
|
||||
ignoreUser(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
import {graphql} from 'react-apollo';
|
||||
import POST_COMMENT from './postComment.graphql';
|
||||
import POST_FLAG from './postFlag.graphql';
|
||||
import POST_DONT_AGREE from './postDontAgree.graphql';
|
||||
import DELETE_ACTION from './deleteAction.graphql';
|
||||
import ADD_COMMENT_TAG from './addCommentTag.graphql';
|
||||
import REMOVE_COMMENT_TAG from './removeCommentTag.graphql';
|
||||
import IGNORE_USER from './ignoreUser.graphql';
|
||||
import STOP_IGNORING_USER from './stopIgnoringUser.graphql';
|
||||
|
||||
import commentView from '../fragments/commentView.graphql';
|
||||
|
||||
export const postComment = graphql(POST_COMMENT, {
|
||||
options: () => ({
|
||||
fragments: commentView
|
||||
}),
|
||||
props: ({ownProps, mutate}) => ({
|
||||
postItem: comment => {
|
||||
const {asset_id, body, parent_id, tags = []} = comment;
|
||||
return mutate({
|
||||
variables: {
|
||||
comment
|
||||
},
|
||||
optimisticResponse: {
|
||||
createComment: {
|
||||
comment: {
|
||||
user: {
|
||||
id: ownProps.auth.user.id,
|
||||
name: ownProps.auth.user.username
|
||||
},
|
||||
created_at: new Date().toISOString(),
|
||||
body,
|
||||
parent_id,
|
||||
asset_id,
|
||||
action_summaries: [],
|
||||
tags,
|
||||
status: null,
|
||||
id: 'pending'
|
||||
}
|
||||
}
|
||||
},
|
||||
updateQueries: {
|
||||
EmbedQuery: (oldData, {mutationResult: {data: {createComment: {comment}}}}) => {
|
||||
|
||||
if (oldData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') {
|
||||
return oldData;
|
||||
}
|
||||
|
||||
let updatedAsset;
|
||||
|
||||
// If posting a reply
|
||||
if (parent_id) {
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
comments: oldData.asset.comments.map((oldComment) => {
|
||||
return oldComment.id === parent_id
|
||||
? {...oldComment, replies: [...oldComment.replies, comment], replyCount: oldComment.replyCount + 1}
|
||||
: oldComment;
|
||||
})
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
// If posting a top-level comment
|
||||
updatedAsset = {
|
||||
...oldData,
|
||||
asset: {
|
||||
...oldData.asset,
|
||||
commentCount: oldData.asset.commentCount + 1,
|
||||
comments: [comment, ...oldData.asset.comments]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return updatedAsset;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
export const postFlag = graphql(POST_FLAG, {
|
||||
props: ({mutate}) => ({
|
||||
postFlag: (flag) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
flag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const postDontAgree = graphql(POST_DONT_AGREE, {
|
||||
props: ({mutate}) => ({
|
||||
postDontAgree: (dontagree) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
dontagree
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const deleteAction = graphql(DELETE_ACTION, {
|
||||
props: ({mutate}) => ({
|
||||
deleteAction: (id) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const addCommentTag = graphql(ADD_COMMENT_TAG, {
|
||||
props: ({mutate}) => ({
|
||||
addCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
export const removeCommentTag = graphql(REMOVE_COMMENT_TAG, {
|
||||
props: ({mutate}) => ({
|
||||
removeCommentTag: ({id, tag}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
tag
|
||||
}
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
export const ignoreUser = graphql(IGNORE_USER, {
|
||||
props: ({mutate}) => ({
|
||||
ignoreUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
]
|
||||
});
|
||||
}}),
|
||||
});
|
||||
|
||||
// TODO: don't rely on refetching.
|
||||
export const stopIgnoringUser = graphql(STOP_IGNORING_USER, {
|
||||
props: ({mutate}) => {
|
||||
return {
|
||||
stopIgnoringUser: ({id}) => {
|
||||
return mutate({
|
||||
variables: {
|
||||
id,
|
||||
},
|
||||
refetchQueries: [
|
||||
'EmbedQuery', 'myIgnoredUsers',
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
#import "../fragments/commentView.graphql"
|
||||
|
||||
mutation CreateComment ($comment: CreateCommentInput!) {
|
||||
createComment(comment: $comment) {
|
||||
comment {
|
||||
...commentView
|
||||
replyCount
|
||||
replies {
|
||||
...commentView
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
mutation CreateDontAgree($dontagree: CreateDontAgreeInput!) {
|
||||
createDontAgree(dontagree:$dontagree) {
|
||||
dontagree {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
mutation CreateFlag($flag: CreateFlagInput!) {
|
||||
createFlag(flag:$flag) {
|
||||
flag {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
mutation RemoveCommentTag ($id: ID!, $tag: String!) {
|
||||
removeCommentTag(id:$id, tag:$tag) {
|
||||
comment {
|
||||
id
|
||||
tags {
|
||||
name
|
||||
}
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
mutation stopIgnoringUser ($id: ID!) {
|
||||
stopIgnoringUser(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import {graphql} from 'react-apollo';
|
||||
import MY_COMMENT_HISTORY from './myCommentHistory.graphql';
|
||||
import MY_IGNORED_USERS from './myIgnoredUsers.graphql';
|
||||
|
||||
export const myCommentHistory = graphql(MY_COMMENT_HISTORY, {});
|
||||
|
||||
export const myIgnoredUsers = graphql(MY_IGNORED_USERS, {
|
||||
props: ({data}) => {
|
||||
return ({
|
||||
myIgnoredUsersData: data
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,14 +0,0 @@
|
||||
query myCommentHistory {
|
||||
me {
|
||||
comments {
|
||||
id
|
||||
body
|
||||
asset {
|
||||
id
|
||||
title
|
||||
url
|
||||
}
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
query myIgnoredUsers {
|
||||
myIgnoredUsers {
|
||||
id,
|
||||
username,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function getDisplayName(WrappedComponent) {
|
||||
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
||||
}
|
||||
@@ -3,14 +3,14 @@ import merge from 'lodash/merge';
|
||||
import flatten from 'lodash/flatten';
|
||||
import flattenDeep from 'lodash/flattenDeep';
|
||||
import uniq from 'lodash/uniq';
|
||||
import pick from 'lodash/pick';
|
||||
import plugins from 'pluginsConfig';
|
||||
import {gql} from 'react-apollo';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
|
||||
|
||||
export const pluginReducers = merge(
|
||||
...plugins
|
||||
.filter(o => o.module.reducer)
|
||||
.map(o => ({...o.module.reducer}))
|
||||
.filter((o) => o.module.reducer)
|
||||
.map((o) => ({...o.module.reducer}))
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -18,26 +18,35 @@ export const pluginReducers = merge(
|
||||
*/
|
||||
export function getSlotElements(slot, props = {}) {
|
||||
const components = flatten(plugins
|
||||
.filter(o => o.module.slots[slot])
|
||||
.map(o => o.module.slots[slot]));
|
||||
.filter((o) => o.module.slots[slot])
|
||||
.map((o) => o.module.slots[slot]));
|
||||
return components
|
||||
.map((component, i) => React.createElement(component, {...props, key: i}));
|
||||
.map((component, i) => React.createElement(component, {key: i, ...props}));
|
||||
}
|
||||
|
||||
function getComponentFragments(components) {
|
||||
return components
|
||||
.map(c => c.fragments)
|
||||
.filter(fragments => fragments)
|
||||
const res = components
|
||||
.map((c) => c.fragments)
|
||||
.filter((fragments) => fragments)
|
||||
.reduce((res, fragments) => {
|
||||
Object.keys(fragments).forEach(key => {
|
||||
Object.keys(fragments).forEach((key) => {
|
||||
if (!(key in res)) {
|
||||
res[key] = {spreads: '', definitions: ''};
|
||||
res[key] = {spreads: [], definitions: []};
|
||||
}
|
||||
res[key].spreads += `...${getDefinitionName(fragments[key])}\n`;
|
||||
res[key].definitions = gql`${res[key].definitions}${fragments[key]}`;
|
||||
res[key].spreads.push(getDefinitionName(fragments[key]));
|
||||
res[key].definitions.push(fragments[key]);
|
||||
});
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
Object.keys(res).forEach((key) => {
|
||||
|
||||
// Assemble arguments for `gql` to call it directly without using template literals.
|
||||
res[key].spreads = `...${res[key].spreads.join('\n...')}\n`;
|
||||
res[key].definitions = mergeDocuments(res[key].definitions);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,10 +65,10 @@ export function getSlotsFragments(slots) {
|
||||
if (!Array.isArray(slots)) {
|
||||
slots = [slots];
|
||||
}
|
||||
const components = uniq(flattenDeep(slots.map(slot => {
|
||||
const components = uniq(flattenDeep(slots.map((slot) => {
|
||||
return plugins
|
||||
.filter(o => o.module.slots[slot])
|
||||
.map(o => o.module.slots[slot]);
|
||||
.filter((o) => o.module.slots[slot])
|
||||
.map((o) => o.module.slots[slot]);
|
||||
})));
|
||||
|
||||
const fragments = getComponentFragments(components);
|
||||
@@ -73,3 +82,9 @@ export function getSlotsFragments(slots) {
|
||||
};
|
||||
}
|
||||
|
||||
export function getGraphQLExtensions() {
|
||||
return plugins
|
||||
.map((o) => pick(o.module, ['mutations', 'queries', 'fragments']))
|
||||
.filter((o) => o);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,31 +1,22 @@
|
||||
export const base = '/api/v1';
|
||||
import * as Storage from './storage';
|
||||
|
||||
const buildOptions = (inputOptions = {}) => {
|
||||
|
||||
const csurfDOM = document.head.querySelector('[property=csrf]');
|
||||
|
||||
const defaultOptions = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${Storage.getItem('token')}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
_csrf: csurfDOM ? csurfDOM.content : false
|
||||
credentials: 'same-origin'
|
||||
};
|
||||
|
||||
let options = Object.assign({}, defaultOptions, inputOptions);
|
||||
options.headers = Object.assign({}, defaultOptions.headers, inputOptions.headers);
|
||||
|
||||
if (options._csrf) {
|
||||
switch (options.method.toLowerCase()) {
|
||||
case 'post':
|
||||
case 'put':
|
||||
case 'delete':
|
||||
options.headers['x-csrf-token'] = options._csrf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
options.headers = Object.assign(
|
||||
{},
|
||||
defaultOptions.headers,
|
||||
inputOptions.headers
|
||||
);
|
||||
|
||||
if (options.method.toLowerCase() !== 'get') {
|
||||
options.body = JSON.stringify(options.body);
|
||||
@@ -34,9 +25,9 @@ const buildOptions = (inputOptions = {}) => {
|
||||
return options;
|
||||
};
|
||||
|
||||
const handleResp = res => {
|
||||
const handleResp = (res) => {
|
||||
if (res.status > 399) {
|
||||
return res.json().then(err => {
|
||||
return res.json().then((err) => {
|
||||
let message = err.message || res.status;
|
||||
const error = new Error();
|
||||
|
||||
@@ -58,6 +49,8 @@ const handleResp = res => {
|
||||
}
|
||||
};
|
||||
|
||||
export const base = '/api/v1';
|
||||
|
||||
export default (url, options) => {
|
||||
return fetch(`${base}${url}`, buildOptions(options)).then(handleResp);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
let available, error;
|
||||
|
||||
function storageAvailable(type) {
|
||||
let storage = window[type], x = '__storage_test__';
|
||||
try {
|
||||
storage.setItem(x, x);
|
||||
storage.removeItem(x);
|
||||
return true;
|
||||
} catch (e) {
|
||||
error = e;
|
||||
return (
|
||||
e instanceof DOMException &&
|
||||
|
||||
// everything except Firefox
|
||||
(e.code === 22 ||
|
||||
|
||||
// Firefox
|
||||
|
||||
e.code === 1014 ||
|
||||
|
||||
// test name field too, because code might not be present
|
||||
|
||||
// everything except Firefox
|
||||
e.name === 'QuotaExceededError' ||
|
||||
|
||||
// Firefox
|
||||
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
|
||||
|
||||
// acknowledge QuotaExceededError only if there's something already stored
|
||||
storage.length !== 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function lazyCheckStorage() {
|
||||
if (typeof available === 'undefined') {
|
||||
available = storageAvailable('localStorage');
|
||||
}
|
||||
}
|
||||
|
||||
export function getItem(item = '') {
|
||||
lazyCheckStorage();
|
||||
|
||||
if (available) {
|
||||
return localStorage.getItem(item);
|
||||
} else {
|
||||
console.error(
|
||||
`Cannot get from localStorage. localStorage is not available. ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function setItem(item = '', value) {
|
||||
lazyCheckStorage();
|
||||
|
||||
if (available) {
|
||||
return localStorage.setItem(item, value);
|
||||
} else {
|
||||
console.error(
|
||||
`Cannot set localStorage. localStorage is not available. ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeItem(item = '') {
|
||||
lazyCheckStorage();
|
||||
|
||||
if (available) {
|
||||
return localStorage.removeItem(item);
|
||||
} else {
|
||||
console.error(
|
||||
`Cannot remove item from localStorage. localStorage is not available. ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function clear() {
|
||||
lazyCheckStorage();
|
||||
|
||||
if (available) {
|
||||
return localStorage.clear();
|
||||
} else {
|
||||
console.error(
|
||||
`Cannot clear localStorage. localStorage is not available. ${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Enable this to debug WEB Storage events
|
||||
// window.addEventListener('storage', function(e) {
|
||||
// const msg = `${e.key} " was changed in page ${e.url} from ${e.oldValue} to ${e.newValue}`;
|
||||
// console.log(msg);
|
||||
// });
|
||||
@@ -0,0 +1,4 @@
|
||||
export function capitalize(str) {
|
||||
const newString = new String(str);
|
||||
return newString.charAt(0).toUpperCase() + newString.slice(1);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
email: email => (/^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)),
|
||||
password: pass => (/^(?=.{8,}).*$/.test(pass)),
|
||||
email: (email) => (/^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)),
|
||||
password: (pass) => (/^(?=.{8,}).*$/.test(pass)),
|
||||
confirmPassword: () => true,
|
||||
username: username => (/^[a-zA-Z0-9_]+$/.test(username)),
|
||||
organizationName: org => (/^[a-zA-Z0-9_ ]+$/).test(org)
|
||||
username: (username) => (/^[a-zA-Z0-9_]+$/.test(username)),
|
||||
organizationName: (org) => (/^[a-zA-Z0-9_ ]+$/).test(org)
|
||||
};
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export {default as withFragments} from './withFragments';
|
||||
export {default as withMutation} from './withMutation';
|
||||
export {default as withQuery} from './withQuery';
|
||||
export {default as withReaction} from './withReaction';
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import React from 'react';
|
||||
import {getDisplayName} from '../helpers/hoc';
|
||||
|
||||
// TODO: revisit `filtering` after https://github.com/apollographql/graphql-anywhere/issues/38.
|
||||
|
||||
function getDisplayName(WrappedComponent) {
|
||||
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
|
||||
}
|
||||
|
||||
export default fragments => WrappedComponent => {
|
||||
export default (fragments) => (WrappedComponent) => {
|
||||
class WithFragments extends React.Component {
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} />;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import * as React from 'react';
|
||||
import {graphql} from 'react-apollo';
|
||||
import merge from 'lodash/merge';
|
||||
import uniq from 'lodash/uniq';
|
||||
import flatten from 'lodash/flatten';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import {getMutationOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry';
|
||||
import {store} from 'coral-framework/services/store';
|
||||
import {getDefinitionName} from '../utils';
|
||||
|
||||
/**
|
||||
* Exports a HOC with the same signature as `graphql`, that will
|
||||
* apply mutation options registered in the graphRegistry.
|
||||
*/
|
||||
export default (document, config) => (WrappedComponent) => {
|
||||
config = {
|
||||
...config,
|
||||
options: config.options || {},
|
||||
props: config.props || ((data) => ({mutate: data.mutate()})),
|
||||
};
|
||||
const wrappedProps = (data) => {
|
||||
const name = getDefinitionName(document);
|
||||
const callbacks = getMutationOptions(name);
|
||||
const mutate = (base) => {
|
||||
const variables = base.variables || config.options.variables;
|
||||
const configs = callbacks.map((cb) => cb({variables, state: store.getState()}));
|
||||
|
||||
const optimisticResponse = merge(
|
||||
base.optimisticResponse || config.options.optimisticResponse,
|
||||
...configs.map((cfg) => cfg.optimisticResponse),
|
||||
);
|
||||
|
||||
const refetchQueries = flatten(uniq([
|
||||
base.refetchQueries || config.options.refetchQueries,
|
||||
...configs.map((cfg) => cfg.refetchQueries),
|
||||
].filter((i) => i)));
|
||||
|
||||
const updateCallbacks =
|
||||
[base.update || config.options.update]
|
||||
.concat(...configs.map((cfg) => cfg.update))
|
||||
.filter((i) => i);
|
||||
|
||||
const update = (proxy, result) => {
|
||||
updateCallbacks.forEach((cb) => cb(proxy, result));
|
||||
};
|
||||
|
||||
const updateQueries =
|
||||
[
|
||||
base.updateQueries || config.options.updateQueries,
|
||||
...configs.map((cfg) => cfg.updateQueries)
|
||||
]
|
||||
.filter((i) => i)
|
||||
.reduce((res, map) => {
|
||||
Object.keys(map).forEach((key) => {
|
||||
if (!(key in res)) {
|
||||
res[key] = map[key];
|
||||
} else {
|
||||
const existing = res[key];
|
||||
res[key] = (prev, result) => {
|
||||
const next = existing(prev, result);
|
||||
return map[key](next, result);
|
||||
};
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}, {});
|
||||
|
||||
const wrappedConfig = {
|
||||
variables,
|
||||
optimisticResponse,
|
||||
refetchQueries,
|
||||
updateQueries,
|
||||
update,
|
||||
};
|
||||
if (isEmpty(wrappedConfig.optimisticResponse)) {
|
||||
delete wrappedConfig.optimisticResponse;
|
||||
}
|
||||
return data.mutate(wrappedConfig);
|
||||
};
|
||||
return config.props({...data, mutate});
|
||||
};
|
||||
|
||||
// Lazily resolve fragments from graphRegistry to support circular dependencies.
|
||||
let memoized = null;
|
||||
const getWrapped = () => {
|
||||
if (!memoized) {
|
||||
memoized = graphql(resolveFragments(document), {...config, props: wrappedProps})(WrappedComponent);
|
||||
}
|
||||
return memoized;
|
||||
};
|
||||
|
||||
return (props) => {
|
||||
const Wrapped = getWrapped();
|
||||
return <Wrapped {...props} />;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as React from 'react';
|
||||
import {graphql} from 'react-apollo';
|
||||
import {getQueryOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry';
|
||||
import {getDefinitionName, separateDataAndRoot} from '../utils';
|
||||
|
||||
/**
|
||||
* Exports a HOC with the same signature as `graphql`, that will
|
||||
* apply query options registered in the graphRegistry.
|
||||
*/
|
||||
export default (document, config) => (WrappedComponent) => {
|
||||
config = {
|
||||
...config,
|
||||
options: config.options || {},
|
||||
props: config.props || (({data}) => separateDataAndRoot(data)),
|
||||
};
|
||||
|
||||
const wrappedOptions = (data) => {
|
||||
const base = (typeof config.options === 'function') ? config.options(data) : config.options;
|
||||
const name = getDefinitionName(document);
|
||||
const configs = getQueryOptions(name);
|
||||
const reducerCallbacks =
|
||||
[base.reducer || ((i) => i)]
|
||||
.concat(...configs.map((cfg) => cfg.reducer))
|
||||
.filter((i) => i);
|
||||
|
||||
const reducer = reducerCallbacks.reduce(
|
||||
(a, b) => (prev, ...rest) =>
|
||||
b(a(prev, ...rest), ...rest),
|
||||
);
|
||||
|
||||
return {
|
||||
...base,
|
||||
reducer,
|
||||
};
|
||||
};
|
||||
|
||||
let memoized = null;
|
||||
const getWrapped = () => {
|
||||
if (!memoized) {
|
||||
memoized = graphql(resolveFragments(document), {...config, options: wrappedOptions})(WrappedComponent);
|
||||
}
|
||||
return memoized;
|
||||
};
|
||||
|
||||
return (props) => {
|
||||
const Wrapped = getWrapped();
|
||||
return <Wrapped {...props} />;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,243 @@
|
||||
import React from 'react';
|
||||
import get from 'lodash/get';
|
||||
import uuid from 'uuid/v4';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getDisplayName} from '../helpers/hoc';
|
||||
import {compose, gql, graphql} from 'react-apollo';
|
||||
import withFragments from 'coral-framework/hocs/withFragments';
|
||||
import {showSignInDialog} from 'coral-framework/actions/auth';
|
||||
import {capitalize} from 'coral-framework/helpers/strings';
|
||||
import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
|
||||
|
||||
export default (reaction) => (WrappedComponent) => {
|
||||
if (typeof reaction !== 'string') {
|
||||
console.error('Reaction must be a valid string');
|
||||
return null;
|
||||
}
|
||||
|
||||
reaction = reaction.toLowerCase();
|
||||
|
||||
class WithReactions extends React.Component {
|
||||
render() {
|
||||
const {comment} = this.props;
|
||||
|
||||
const reactionSummary = getMyActionSummary(
|
||||
`${capitalize(reaction)}ActionSummary`,
|
||||
comment
|
||||
);
|
||||
|
||||
const count = getTotalActionCount(
|
||||
`${capitalize(reaction)}ActionSummary`,
|
||||
comment
|
||||
);
|
||||
|
||||
const alreadyReacted = () => !!reactionSummary;
|
||||
|
||||
const withReactionProps = {reactionSummary, count, alreadyReacted};
|
||||
|
||||
return <WrappedComponent {...this.props} {...withReactionProps} />;
|
||||
}
|
||||
}
|
||||
|
||||
const isReaction = (a) =>
|
||||
a.__typename === `${capitalize(reaction)}ActionSummary`;
|
||||
|
||||
const COMMENT_FRAGMENT = gql`
|
||||
fragment ${capitalize(reaction)}Button_updateFragment on Comment {
|
||||
action_summaries {
|
||||
... on ${capitalize(reaction)}ActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const withDeleteReaction = graphql(
|
||||
gql`
|
||||
mutation deleteReaction($id: ID!) {
|
||||
deleteAction(id:$id) {
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate, ownProps}) => ({
|
||||
deleteReaction: () => {
|
||||
|
||||
const reactionSummary = getMyActionSummary(
|
||||
`${capitalize(reaction)}ActionSummary`,
|
||||
ownProps.comment
|
||||
);
|
||||
|
||||
const reactionData = {
|
||||
id: reactionSummary.current_user.id,
|
||||
commentId: ownProps.comment.id
|
||||
};
|
||||
|
||||
return mutate({
|
||||
variables: {id: reactionData.id},
|
||||
optimisticResponse: {
|
||||
deleteAction: {
|
||||
__typename: 'DeleteActionResponse',
|
||||
errors: null
|
||||
}
|
||||
},
|
||||
update: (proxy) => {
|
||||
const fragmentId = `Comment_${reactionData.commentId}`;
|
||||
|
||||
// Read the data from our cache for this query.
|
||||
const data = proxy.readFragment({
|
||||
fragment: COMMENT_FRAGMENT,
|
||||
id: fragmentId
|
||||
});
|
||||
|
||||
// Check whether we liked this comment.
|
||||
const idx = data.action_summaries.findIndex(isReaction);
|
||||
if (
|
||||
idx < 0 ||
|
||||
get(data.action_summaries[idx], 'current_user.id') !== reactionData.id
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.action_summaries[idx] = {
|
||||
...data.action_summaries[idx],
|
||||
count: data.action_summaries[idx].count - 1,
|
||||
current_user: null
|
||||
};
|
||||
|
||||
// Write our data back to the cache.
|
||||
proxy.writeFragment({
|
||||
fragment: COMMENT_FRAGMENT,
|
||||
id: fragmentId,
|
||||
data
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const withPostReaction = graphql(
|
||||
gql`
|
||||
mutation create${capitalize(reaction)}($${reaction}: Create${capitalize(reaction)}Input!) {
|
||||
create${capitalize(reaction)}(${reaction}: $${reaction}) {
|
||||
${reaction} {
|
||||
id
|
||||
}
|
||||
errors {
|
||||
translation_key
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{
|
||||
props: ({mutate, ownProps}) => ({
|
||||
postReaction: () => {
|
||||
|
||||
const reactionData = {
|
||||
item_id: ownProps.comment.id,
|
||||
item_type: 'COMMENTS'
|
||||
};
|
||||
|
||||
return mutate({
|
||||
variables: {[reaction]: reactionData},
|
||||
optimisticResponse: {
|
||||
[`create${capitalize(reaction)}`]: {
|
||||
__typename: `Create${capitalize(reaction)}Response`,
|
||||
errors: null,
|
||||
[reaction]: {
|
||||
__typename: `${capitalize(reaction)}Action`,
|
||||
id: uuid()
|
||||
}
|
||||
}
|
||||
},
|
||||
update: (proxy, mutationResult) => {
|
||||
const fragmentId = `Comment_${reactionData.item_id}`;
|
||||
|
||||
// Read the data from our cache for this query.
|
||||
const data = proxy.readFragment({
|
||||
fragment: COMMENT_FRAGMENT,
|
||||
id: fragmentId
|
||||
});
|
||||
|
||||
// Add our comment from the mutation to the end.
|
||||
let idx = data.action_summaries.findIndex(isReaction);
|
||||
|
||||
// Check whether we already reactioned this comment.
|
||||
if (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: `${capitalize(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: mutationResult.data[
|
||||
`create${capitalize(reaction)}`
|
||||
][reaction]
|
||||
};
|
||||
|
||||
// Write our data back to the cache.
|
||||
proxy.writeFragment({
|
||||
fragment: COMMENT_FRAGMENT,
|
||||
id: fragmentId,
|
||||
data
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({showSignInDialog}, dispatch);
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment ${capitalize(reaction)}Button_root on RootQuery {
|
||||
me {
|
||||
status
|
||||
}
|
||||
}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment ${capitalize(reaction)}Button_comment on Comment {
|
||||
action_summaries {
|
||||
... on ${capitalize(reaction)}ActionSummary {
|
||||
count
|
||||
current_user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
}),
|
||||
connect(null, mapDispatchToProps),
|
||||
withDeleteReaction,
|
||||
withPostReaction
|
||||
);
|
||||
|
||||
WithReactions.displayName = `WithReactions(${getDisplayName(WrappedComponent)})`;
|
||||
|
||||
return enhance(WithReactions);
|
||||
};
|
||||
@@ -11,7 +11,7 @@ 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 config.client.map((x) => typeof x === 'string' ? x : Object.keys(x)[0]);
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
@@ -20,7 +20,7 @@ const initialState = Map({
|
||||
fromSignUp: false
|
||||
});
|
||||
|
||||
const purge = user => {
|
||||
const purge = (user) => {
|
||||
const {settings, profiles, ...userData} = user; // eslint-disable-line
|
||||
return fromJS(userData);
|
||||
};
|
||||
@@ -64,9 +64,6 @@ export default function auth (state = initialState, action) {
|
||||
.set('view', action.view);
|
||||
case actions.CLEAN_STATE:
|
||||
return initialState;
|
||||
case actions.CHECK_CSRF_TOKEN:
|
||||
return state
|
||||
.set('_csrf', action._csrf);
|
||||
case actions.FETCH_SIGNIN_REQUEST:
|
||||
return state
|
||||
.set('isLoading', true);
|
||||
@@ -116,7 +113,7 @@ export default function auth (state = initialState, action) {
|
||||
return state
|
||||
.set('isLoading', false)
|
||||
.set('successSignUp', true);
|
||||
case actions.LOGOUT_SUCCESS:
|
||||
case actions.LOGOUT:
|
||||
return state
|
||||
.set('user', null)
|
||||
.set('isLoading', false)
|
||||
|
||||
@@ -12,7 +12,7 @@ const initialState = Map({
|
||||
ignoredUsers: Set(),
|
||||
});
|
||||
|
||||
const purge = user => {
|
||||
const purge = (user) => {
|
||||
const {_id, created_at, updated_at, __v, roles, ...userData} = user; // eslint-disable-line
|
||||
return userData;
|
||||
};
|
||||
@@ -42,9 +42,9 @@ export default function user (state = initialState, action) {
|
||||
case 'APOLLO_MUTATION_RESULT':
|
||||
switch (action.operationName) {
|
||||
case 'ignoreUser':
|
||||
return state.updateIn(['ignoredUsers'], i => i.add(action.variables.id));
|
||||
return state.updateIn(['ignoredUsers'], (i) => i.add(action.variables.id));
|
||||
case 'stopIgnoringUser':
|
||||
return state.updateIn(['ignoredUsers'], i => i.delete(action.variables.id));
|
||||
return state.updateIn(['ignoredUsers'], (i) => i.delete(action.variables.id));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ApolloClient, {addTypename} from 'apollo-client';
|
||||
import getNetworkInterface from './transport';
|
||||
import {networkInterface} from './transport';
|
||||
|
||||
// import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';
|
||||
|
||||
@@ -11,8 +11,6 @@ import getNetworkInterface from './transport';
|
||||
// getNetworkInterface(),
|
||||
// wsClient,
|
||||
// );
|
||||
const networkInterface = getNetworkInterface();
|
||||
|
||||
export const client = new ApolloClient({
|
||||
connectToDevTools: true,
|
||||
queryTransformer: addTypename,
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import {getDefinitionName, mergeDocuments} from 'coral-framework/utils';
|
||||
import {getGraphQLExtensions} from 'coral-framework/helpers/plugins';
|
||||
import globalFragments from 'coral-framework/graphql/fragments';
|
||||
import uniq from 'lodash/uniq';
|
||||
|
||||
const fragments = {};
|
||||
const mutationOptions = {};
|
||||
const queryOptions = {};
|
||||
|
||||
const getTypeName = (ast) => ast.definitions[0].typeCondition.name.value;
|
||||
|
||||
/**
|
||||
* Add fragment
|
||||
*
|
||||
* Example:
|
||||
* addFragment('MyFragment', gql`
|
||||
* fragment Plugin_MyFragment on Comment {
|
||||
* body
|
||||
* }
|
||||
* `);
|
||||
*/
|
||||
export function addFragment(key, document) {
|
||||
const type = getTypeName(document);
|
||||
const name = getDefinitionName(document);
|
||||
if (!(key in fragments)) {
|
||||
fragments[key] = {type, names: [name], documents: [document]};
|
||||
} else {
|
||||
if (type !== fragments[key].type) {
|
||||
console.error(`Type mismatch ${type} !== ${fragments[key].type}`);
|
||||
}
|
||||
fragments[key].names.push(name);
|
||||
fragments[key].documents.push(document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add mutation options.
|
||||
*
|
||||
* Example:
|
||||
* // state is the current redux state, which is sometimes
|
||||
* // necessary to fill the optimistic response.
|
||||
* addMutationOptions('PostComment', ({variables, state}) => ({
|
||||
* optimisticResponse: {
|
||||
* CreateComment: {
|
||||
* extra: '',
|
||||
* },
|
||||
* },
|
||||
* refetchQueries: [],
|
||||
* updateQueries: {
|
||||
* EmbedQuery: (previous, data) => {
|
||||
* return previous;
|
||||
* },
|
||||
* },
|
||||
* update: (proxy, result) => {
|
||||
* },
|
||||
* })
|
||||
*/
|
||||
export function addMutationOptions(key, config) {
|
||||
if (!(key in mutationOptions)) {
|
||||
mutationOptions[key] = [config];
|
||||
} else {
|
||||
mutationOptions[key].push(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add query options.
|
||||
*
|
||||
* Example:
|
||||
* addQueryOptions('EmbedQuery', {
|
||||
* reducer: (previousResult, action, variables) => previousResult,
|
||||
* });
|
||||
*/
|
||||
export function addQueryOptions(key, config) {
|
||||
if (!(key in queryOptions)) {
|
||||
queryOptions[key] = [config];
|
||||
} else {
|
||||
queryOptions[key].push(config);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all fragments, mutation options, and query options defined in the object.
|
||||
*
|
||||
* Example:
|
||||
* add({
|
||||
* fragments: {
|
||||
* CreateCommentResponse: gql`
|
||||
* fragment CoralRandomEmoji_CreateCommentResponse on CreateCommentResponse {
|
||||
* [...]
|
||||
* }`,
|
||||
* },
|
||||
* mutations: {
|
||||
* // state is the current redux state, which is sometimes
|
||||
* // necessary to fill the optimistic response.
|
||||
* PostComment: ({variables, state}) => ({
|
||||
* optimisticResponse: {
|
||||
* [...]
|
||||
* },
|
||||
* refetchQueries: [],
|
||||
* updateQueries: {
|
||||
* EmbedQuery: (previous, data) => {
|
||||
* return previous;
|
||||
* },
|
||||
* },
|
||||
* update: (proxy, result) => {
|
||||
* },
|
||||
* })
|
||||
* },
|
||||
* queries: {
|
||||
* EmbedQuery: {
|
||||
* reducer: (previousResult, action, variables) => {
|
||||
* return previousResult;
|
||||
* },
|
||||
* },
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function add(extension) {
|
||||
Object.keys(extension.fragments || []).forEach((key) => addFragment(key, extension.fragments[key]));
|
||||
Object.keys(extension.mutations || []).forEach((key) => addMutationOptions(key, extension.mutations[key]));
|
||||
Object.keys(extension.queries || []).forEach((key) => addQueryOptions(key, extension.queries[key]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of mutation options.
|
||||
*/
|
||||
export function getMutationOptions(key) {
|
||||
init();
|
||||
return mutationOptions[key] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of query options.
|
||||
*/
|
||||
export function getQueryOptions(key) {
|
||||
init();
|
||||
return queryOptions[key] || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a document with a fragment named `key`, which contains
|
||||
* all fragments added under this key.
|
||||
*/
|
||||
export function getFragmentDocument(key) {
|
||||
init();
|
||||
|
||||
if (!(key in fragments)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let documents = fragments[key] ? fragments[key].documents : [];
|
||||
let fields = fragments[key] ? `...${fragments[key].names.join('\n...')}\n` : ' __typename';
|
||||
|
||||
// Assemble arguments for `gql` to call it directly without using template literals.
|
||||
const main = `
|
||||
fragment ${key} on ${fragments[key].type} {
|
||||
${fields}
|
||||
}
|
||||
`;
|
||||
return mergeDocuments([main, ...documents]);
|
||||
}
|
||||
|
||||
// The fragments and configs are lazily loaded to allow circular dependencies to work.
|
||||
// TODO: We might want to change this to an explicit add after we have lazy Queries and Mutations.
|
||||
let initialized = false;
|
||||
|
||||
function init() {
|
||||
if (initialized) { return; }
|
||||
initialized = true;
|
||||
|
||||
// Add fragments from framework.
|
||||
[globalFragments].forEach((map) =>
|
||||
Object.keys(map).forEach((key) => addFragment(key, map[key]))
|
||||
);
|
||||
|
||||
// Add configs from plugins.
|
||||
getGraphQLExtensions().forEach((ext) => add(ext));
|
||||
}
|
||||
|
||||
export function resolveFragments(document) {
|
||||
if (document.loc.source) {
|
||||
|
||||
// resolve fragments from registry
|
||||
const matchedSubFragments = document.loc.source.body.match(/\.\.\.(.*)/g) || [];
|
||||
const subFragments =
|
||||
uniq(matchedSubFragments.map((f) => f.replace('...', '')))
|
||||
.map((key) => getFragmentDocument(key))
|
||||
.filter((i) => i);
|
||||
|
||||
if (subFragments.length > 0) {
|
||||
return mergeDocuments([document, ...subFragments]);
|
||||
}
|
||||
} else {
|
||||
console.warn('Can only resolve fragments from documents definied using the gql tag.');
|
||||
}
|
||||
return document;
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import thunk from 'redux-thunk';
|
||||
import mainReducer from '../reducers';
|
||||
import {client} from './client';
|
||||
|
||||
const apolloErrorReporter = () => next => action => {
|
||||
const apolloErrorReporter = () => (next) => (action) => {
|
||||
if (action.type === 'APOLLO_QUERY_ERROR') {
|
||||
console.error(action.error);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
import {createNetworkInterface} from 'apollo-client';
|
||||
import * as Storage from '../helpers/storage';
|
||||
|
||||
export default function getNetworkInterface(apiUrl = '/api/v1/graph/ql', headers = {}) {
|
||||
return new createNetworkInterface({
|
||||
uri: apiUrl,
|
||||
opts: {
|
||||
credentials: 'same-origin',
|
||||
headers,
|
||||
},
|
||||
});
|
||||
}
|
||||
//==============================================================================
|
||||
// NETWORK INTERFACE
|
||||
//==============================================================================
|
||||
|
||||
const networkInterface = createNetworkInterface({
|
||||
uri: '/api/v1/graph/ql',
|
||||
opts: {
|
||||
credentials: 'same-origin'
|
||||
}
|
||||
});
|
||||
|
||||
//==============================================================================
|
||||
// MIDDLEWARES
|
||||
//==============================================================================
|
||||
|
||||
networkInterface.use([{
|
||||
applyMiddleware(req, next) {
|
||||
if (!req.options.headers) {
|
||||
req.options.headers = {}; // Create the header object if needed.
|
||||
}
|
||||
req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`;
|
||||
next();
|
||||
}
|
||||
}]);
|
||||
|
||||
export {
|
||||
networkInterface
|
||||
};
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"en": {
|
||||
"MY_COMMENTS": "My Comments",
|
||||
"profile": "Profile",
|
||||
"myProfile": "My profile",
|
||||
"successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
|
||||
"successNameUpdate": "Your username has been updated",
|
||||
"contentNotAvailable": "This content is not available",
|
||||
"bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Report, or write comments. Please contact us if you have any questions.",
|
||||
"editName": {
|
||||
"msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. Please contact us if you have any questions.",
|
||||
"label": "New Username",
|
||||
"button": "Submit",
|
||||
"error": "Usernames can contain letters, numbers and _ only"
|
||||
},
|
||||
"viewMoreComments": "view more comments",
|
||||
"showAllComments": "Show all comments",
|
||||
"viewReply": "view reply",
|
||||
"viewAllRepliesInitial": "view all {0} replies",
|
||||
"viewAllReplies": "view {0} replies",
|
||||
"newCount": "View {0} new {1}",
|
||||
"comment": "comment",
|
||||
"comments": "comments",
|
||||
"commentIsIgnored": "This comment is hidden because you ignored this user.",
|
||||
"editComment": {
|
||||
"bodyInputLabel": "Edit this comment",
|
||||
"saveButton": "Save changes",
|
||||
"editWindowExpired": "You can no longer edit this comment. The time window to do so has expired. Why not post another one?",
|
||||
"editWindowExpiredClose": "Close",
|
||||
"editWindowTimerPrefix": "Edit Window: ",
|
||||
"second": "second",
|
||||
"secondsPlural": "seconds",
|
||||
"unexpectedError": "Unexpected error while saving changes. Sorry!"
|
||||
},
|
||||
"error": {
|
||||
"COMMENT_TOO_SHORT": "Your comment must have something in it",
|
||||
"EDIT_WINDOW_ENDED": "You can no longer edit this comment. The time window to do so has expired.",
|
||||
"emailNotVerified": "Email address {0} not verified.",
|
||||
"email": "Not a valid E-Mail",
|
||||
"networkError": "Failed to connect to server. Check your internet connection and try again.",
|
||||
"password": "Password must be at least 8 characters",
|
||||
"username": "Usernames can contain letters, numbers and _ only",
|
||||
"confirmPassword": "Passwords don't match. Please, check again",
|
||||
"organizationName": "Organization name must only contain letters or numbers.",
|
||||
"emailPasswordError": "Email and/or password combination incorrect.",
|
||||
"EMAIL_REQUIRED": "An email address is required",
|
||||
"PASSWORD_REQUIRED": "Must input a password",
|
||||
"PASSWORD_LENGTH": "Password is too short",
|
||||
"EMAIL_IN_USE": "Email address already in use",
|
||||
"EMAIL_USERNAME_IN_USE": "Email address or username already in use",
|
||||
"USERNAME_IN_USE": "Username already in use",
|
||||
"USERNAME_REQUIRED": "Must input a username",
|
||||
"NO_SPECIAL_CHARACTERS": "Usernames can contain letters, numbers and _ only",
|
||||
"PROFANITY_ERROR": "Usernames must not contain profanity. Please contact the administrator if you believe this to be in error.",
|
||||
"NOT_AUTHORIZED": "Not authorized.",
|
||||
"EDIT_USERNAME_NOT_AUTHORIZED": "You do not have permission to update your username."
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"profile": "Pérfil",
|
||||
"MY_COMMENTS": "Mis Comentarios",
|
||||
"myProfile": "Mi pérfil",
|
||||
"successUpdateSettings": "La configuración de este articulo fue actualizada",
|
||||
"successBioUpdate": "Tu biografia fue actualizada",
|
||||
"contentNotAvailable": "El contenido no se encuentra disponible",
|
||||
"bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes gustar, marcar o escribir commentarios.",
|
||||
"editNameMsg": "",
|
||||
"viewMoreComments": "Ver commentarios más",
|
||||
"viewReply": "ver respuesta",
|
||||
"viewAllRepliesInitial": "ver todas las {0} respuestas",
|
||||
"viewAllReplies": "ver {0} respuestas",
|
||||
"newCount": "Ver {0} {1} más",
|
||||
"comment": "commentario",
|
||||
"comments": "commentarios",
|
||||
"commentIsIgnored": "Este comentario está escondido porque has ignorado al usuario.",
|
||||
"showAllComments": "Mostrar todos los comentarios",
|
||||
"editComment": {
|
||||
"bodyInputLabel": "Editar este comentario",
|
||||
"saveButton": "Guardar cambios",
|
||||
"editWindowExpired": "Ya no puedes editar este comentario. La ventana de tiempo para hacerlo ha caducado. ¿Por qué no publicar otro?",
|
||||
"editWindowExpiredClose": "Cerca",
|
||||
"editWindowTimerPrefix": "Ventana de edición: ",
|
||||
"second": "segundo",
|
||||
"secondsPlural": "segundos",
|
||||
"unexpectedError": "Unexpected error while saving changes. Sorry!"
|
||||
},
|
||||
"error": {
|
||||
"editWindowExpired": "Ya no puedes editar este comentario. La ventana de tiempo para hacerlo ha caducado.",
|
||||
"emailNotVerified": "E-mail {0} no verificado.",
|
||||
"email": "No es un e-mail válido",
|
||||
"networkError": "Error al conectar con el servidor. Compruebe su conexión a Internet y vuelva a intentarlo.",
|
||||
"password": "La contraseña debe tener por lo menos 8 caracteres",
|
||||
"username": "Los nombres pueden contener letras, números y _",
|
||||
"organizationName": "El nombre de la organización debe contener letras y/o números.",
|
||||
"confirmPassword": "Las contraseñas no coinciden",
|
||||
"emailPasswordError": "E-mail y/o contraseña incorrecta.",
|
||||
"EMAIL_REQUIRED": "Se requiere un e-mail",
|
||||
"PASSWORD_REQUIRED": "Debe ingresar una contraseña",
|
||||
"PASSWORD_LENGTH": "La contraseña es muy corta",
|
||||
"EMAIL_IN_USE": "El e-mail se encuentra en uso",
|
||||
"EMAIL_USERNAME_IN_USE": "E-mail o Nombre en uso.",
|
||||
"USERNAME_IN_USE": "Nombre en uso.",
|
||||
"USERNAME_REQUIRED": "Debe ingresar un nombre",
|
||||
"NO_SPECIAL_CHARACTERS": "Los nombres pueden contener letras, números y _",
|
||||
"PROFANITY_ERROR": "Los nombres no pueden contener blasfemias. Por favor contacte al o la administradora si cree que esto es un error",
|
||||
"NOT_AUTHORIZED": "Acción no autorizada.",
|
||||
"EDIT_USERNAME_NOT_AUTHORIZED": "No tiene permiso para editar el nombre de usuario."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import {gql} from 'react-apollo';
|
||||
|
||||
export const getTotalActionCount = (type, comment) => {
|
||||
return comment.action_summaries
|
||||
.filter(s => s.__typename === type)
|
||||
.filter((s) => s.__typename === type)
|
||||
.reduce((total, summary) => {
|
||||
return total + summary.count;
|
||||
}, 0);
|
||||
@@ -10,14 +12,14 @@ export const iPerformedThisAction = (type, comment) => {
|
||||
|
||||
// if there is a current_user on any of the ActionSummary(s), the user performed this action
|
||||
return comment.action_summaries
|
||||
.filter(a => a.__typename === type)
|
||||
.some(a => a.current_user);
|
||||
.filter((a) => a.__typename === type)
|
||||
.some((a) => a.current_user);
|
||||
};
|
||||
|
||||
export const getMyActionSummary = (type, comment) => {
|
||||
return comment.action_summaries
|
||||
.filter(a => a.__typename === type)
|
||||
.find(a => a.current_user);
|
||||
.filter((a) => a.__typename === type)
|
||||
.find((a) => a.current_user);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -27,7 +29,7 @@ export const getMyActionSummary = (type, comment) => {
|
||||
*/
|
||||
|
||||
export const getActionSummary = (type, comment) => {
|
||||
return comment.action_summaries.filter(a => a.__typename === type);
|
||||
return comment.action_summaries.filter((a) => a.__typename === type);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -61,3 +63,11 @@ export function separateDataAndRoot(
|
||||
root,
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeDocuments(documents) {
|
||||
const main = typeof documents[0] === 'string' ? documents[0] : documents[0].loc.source.body;
|
||||
const substitutions = documents.slice(1);
|
||||
const literals = [main, ...substitutions.map(() => '\n')];
|
||||
return gql.apply(null, [literals, ...substitutions]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user