mirror of
https://github.com/wassname/talk.git
synced 2026-07-26 13:37:38 +08:00
replaced eslint:recommended with prettier
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {connect} from 'react-redux';
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { compose } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import errorMsj from 'coral-framework/helpers/error';
|
||||
import validate from 'coral-framework/helpers/validate';
|
||||
import CreateUsernameDialog from './CreateUsernameDialog';
|
||||
import {withSetUsername} from 'coral-framework/graphql/mutations';
|
||||
import {forEachError} from 'plugin-api/beta/client/utils';
|
||||
import { withSetUsername } from 'coral-framework/graphql/mutations';
|
||||
import { forEachError } from 'plugin-api/beta/client/utils';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
@@ -25,32 +25,36 @@ class ChangeUsernameContainer extends React.Component {
|
||||
|
||||
this.state = {
|
||||
formData: {
|
||||
username: (props.auth.user && props.auth.user.username) || ''
|
||||
username: (props.auth.user && props.auth.user.username) || '',
|
||||
},
|
||||
errors: {},
|
||||
showErrors: false
|
||||
showErrors: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(next) {
|
||||
if (!this.props.auth.showCreateUsernameDialog && next.auth.showCreateUsernameDialog) {
|
||||
if (
|
||||
!this.props.auth.showCreateUsernameDialog &&
|
||||
next.auth.showCreateUsernameDialog
|
||||
) {
|
||||
this.setState({
|
||||
formData: {
|
||||
username: (this.props.auth.user && this.props.auth.user.username) || '',
|
||||
username:
|
||||
(this.props.auth.user && this.props.auth.user.username) || '',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
const {name, value} = e.target;
|
||||
handleChange = e => {
|
||||
const { name, value } = e.target;
|
||||
this.setState(
|
||||
(state) => ({
|
||||
state => ({
|
||||
...state,
|
||||
formData: {
|
||||
...state.formData,
|
||||
[name]: value
|
||||
}
|
||||
[name]: value,
|
||||
},
|
||||
}),
|
||||
() => {
|
||||
this.validation(name, value);
|
||||
@@ -59,16 +63,16 @@ class ChangeUsernameContainer extends React.Component {
|
||||
};
|
||||
|
||||
addError = (name, error) => {
|
||||
return this.setState((state) => ({
|
||||
return this.setState(state => ({
|
||||
errors: {
|
||||
...state.errors,
|
||||
[name]: error
|
||||
}
|
||||
[name]: error,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
validation = (name, value) => {
|
||||
const {addError} = this;
|
||||
const { addError } = this;
|
||||
|
||||
if (!value.length) {
|
||||
addError(name, t('createdisplay.required_field'));
|
||||
@@ -77,23 +81,28 @@ class ChangeUsernameContainer extends React.Component {
|
||||
} else {
|
||||
const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line
|
||||
// Removes Error
|
||||
this.setState((state) => ({...state, errors}));
|
||||
this.setState(state => ({ ...state, errors }));
|
||||
}
|
||||
};
|
||||
|
||||
isCompleted = () => {
|
||||
const {formData} = this.state;
|
||||
return !Object.keys(formData).filter((prop) => !formData[prop].length).length;
|
||||
const { formData } = this.state;
|
||||
return !Object.keys(formData).filter(prop => !formData[prop].length).length;
|
||||
};
|
||||
|
||||
displayErrors = (show = true) => {
|
||||
this.setState({showErrors: show});
|
||||
this.setState({ showErrors: show });
|
||||
};
|
||||
|
||||
async setUsernameAndClose(username, props = this.props) {
|
||||
const {validForm, invalidForm, setUsername, hideCreateUsernameDialog, updateUsername} = props;
|
||||
const {
|
||||
validForm,
|
||||
invalidForm,
|
||||
setUsername,
|
||||
hideCreateUsernameDialog,
|
||||
updateUsername,
|
||||
} = props;
|
||||
try {
|
||||
|
||||
// Perform mutation
|
||||
await setUsername(this.props.auth.user.id, username);
|
||||
|
||||
@@ -102,18 +111,17 @@ class ChangeUsernameContainer extends React.Component {
|
||||
|
||||
hideCreateUsernameDialog();
|
||||
validForm();
|
||||
}
|
||||
catch(error) {
|
||||
} catch (error) {
|
||||
const msgs = [];
|
||||
forEachError(error, ({msg}) => msgs.push(msg));
|
||||
forEachError(error, ({ msg }) => msgs.push(msg));
|
||||
invalidForm(t(msgs.join(', ')));
|
||||
}
|
||||
}
|
||||
|
||||
handleSubmitUsername = (e) => {
|
||||
handleSubmitUsername = e => {
|
||||
e.preventDefault();
|
||||
const {errors, formData: {username}} = this.state;
|
||||
const {invalidForm} = this.props;
|
||||
const { errors, formData: { username } } = this.state;
|
||||
const { invalidForm } = this.props;
|
||||
this.displayErrors();
|
||||
if (this.isCompleted() && !Object.keys(errors).length) {
|
||||
this.setUsernameAndClose(username);
|
||||
@@ -127,7 +135,7 @@ class ChangeUsernameContainer extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {loggedIn, auth} = this.props;
|
||||
const { loggedIn, auth } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<CreateUsernameDialog
|
||||
@@ -153,11 +161,11 @@ ChangeUsernameContainer.propTypes = {
|
||||
changeUsername: PropTypes.func,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({auth}) => ({
|
||||
auth: auth
|
||||
const mapStateToProps = ({ auth }) => ({
|
||||
auth: auth,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
showCreateUsernameDialog,
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './styles.css';
|
||||
import {Dialog, Alert, TextField, Button} from 'plugin-api/beta/client/components/ui';
|
||||
import {FakeComment} from './FakeComment';
|
||||
import {
|
||||
Dialog,
|
||||
Alert,
|
||||
TextField,
|
||||
Button,
|
||||
} from 'plugin-api/beta/client/components/ui';
|
||||
import { FakeComment } from './FakeComment';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const CreateUsernameDialog = ({
|
||||
@@ -18,12 +23,12 @@ const CreateUsernameDialog = ({
|
||||
id="createUsernameDialog"
|
||||
open={open}
|
||||
>
|
||||
<span className={styles.close} onClick={handleClose}>×</span>
|
||||
<span className={styles.close} onClick={handleClose}>
|
||||
×
|
||||
</span>
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
<h1>
|
||||
{t('createdisplay.write_your_username')}
|
||||
</h1>
|
||||
<h1>{t('createdisplay.write_your_username')}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<p className={styles.yourusername}>
|
||||
@@ -40,14 +45,16 @@ const CreateUsernameDialog = ({
|
||||
</p>
|
||||
{props.auth.error && <Alert>{props.auth.error}</Alert>}
|
||||
<form id="saveUsername" onSubmit={handleSubmitUsername}>
|
||||
{props.errors.username &&
|
||||
{props.errors.username && (
|
||||
<span className={styles.hint}>
|
||||
{' '}{t('createdisplay.special_characters')}{' '}
|
||||
</span>}
|
||||
{' '}
|
||||
{t('createdisplay.special_characters')}{' '}
|
||||
</span>
|
||||
)}
|
||||
<div className={styles.saveusername}>
|
||||
<TextField
|
||||
id="username"
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
type="string"
|
||||
label={t('createdisplay.username')}
|
||||
value={formData.username}
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
import React from 'react';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {ReplyButton} from 'talk-plugin-replies';
|
||||
import { ReplyButton } from 'talk-plugin-replies';
|
||||
import styles from './FakeComment.css';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import {CommentTimestamp} from 'plugin-api/beta/client/components';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import { CommentTimestamp } from 'plugin-api/beta/client/components';
|
||||
|
||||
export const FakeComment = ({username, created_at, body}) => (
|
||||
export const FakeComment = ({ username, created_at, body }) => (
|
||||
<div className={styles.root}>
|
||||
<span className={styles.authorName}>
|
||||
{username}
|
||||
</span>
|
||||
<span className={styles.authorName}>{username}</span>
|
||||
<CommentTimestamp created_at={created_at} />
|
||||
<div className={styles.body}>
|
||||
{body}
|
||||
</div>
|
||||
<div className={styles.body}>{body}</div>
|
||||
<div className={styles.footer}>
|
||||
<div>
|
||||
<button className={styles.button}>
|
||||
<span className={styles.label}>
|
||||
{t('like')}
|
||||
</span>
|
||||
<span className={styles.label}>{t('like')}</span>
|
||||
<Icon name="thumb_up" className={styles.icon} />
|
||||
</button>
|
||||
<ReplyButton
|
||||
@@ -30,19 +24,14 @@ export const FakeComment = ({username, created_at, body}) => (
|
||||
</div>
|
||||
<div>
|
||||
<button className={styles.button}>
|
||||
<span className={styles.label}>
|
||||
{t('permalink')}
|
||||
</span>
|
||||
<span className={styles.label}>{t('permalink')}</span>
|
||||
<Icon name="link" className={styles.icon} />
|
||||
</button>
|
||||
<button className={styles.button}>
|
||||
<span className={styles.label}>
|
||||
{t('report')}
|
||||
</span>
|
||||
<span className={styles.label}>{t('report')}</span>
|
||||
<Icon name="flag" className={styles.icon} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './styles.css';
|
||||
import {Button, TextField} from 'plugin-api/beta/client/components/ui';
|
||||
import { Button, TextField } from 'plugin-api/beta/client/components/ui';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class ForgotContent extends React.Component {
|
||||
|
||||
state = {value: ''};
|
||||
state = { value: '' };
|
||||
|
||||
handleSubmit = (e) => {
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
this.props.fetchForgotPassword(this.state.value);
|
||||
};
|
||||
|
||||
handleChangeEmail = (e) => {
|
||||
const {value} = e.target;
|
||||
this.setState({value});
|
||||
}
|
||||
handleChangeEmail = e => {
|
||||
const { value } = e.target;
|
||||
this.setState({ value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const {changeView, auth} = this.props;
|
||||
const {passwordRequestSuccess, passwordRequestFailure} = auth;
|
||||
const { changeView, auth } = this.props;
|
||||
const { passwordRequestSuccess, passwordRequestFailure } = auth;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -31,7 +30,7 @@ class ForgotContent extends React.Component {
|
||||
<div className={styles.textField}>
|
||||
<TextField
|
||||
type="email"
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
id="email"
|
||||
name="email"
|
||||
label={t('sign_in.email')}
|
||||
@@ -47,31 +46,25 @@ class ForgotContent extends React.Component {
|
||||
>
|
||||
{t('sign_in.recover_password')}
|
||||
</Button>
|
||||
{passwordRequestSuccess
|
||||
? <p className={styles.passwordRequestSuccess}>
|
||||
{passwordRequestSuccess ? (
|
||||
<p className={styles.passwordRequestSuccess}>
|
||||
{passwordRequestSuccess}
|
||||
</p>
|
||||
: null}
|
||||
{passwordRequestFailure
|
||||
? <p className={styles.passwordRequestFailure}>
|
||||
) : null}
|
||||
{passwordRequestFailure ? (
|
||||
<p className={styles.passwordRequestFailure}>
|
||||
{passwordRequestFailure}
|
||||
</p>
|
||||
: null}
|
||||
) : null}
|
||||
</form>
|
||||
<div className={styles.footer}>
|
||||
<span>
|
||||
{t('sign_in.need_an_account')}
|
||||
{' '}
|
||||
<a onClick={() => changeView('SIGNUP')}>
|
||||
{t('sign_in.register')}
|
||||
</a>
|
||||
{t('sign_in.need_an_account')}{' '}
|
||||
<a onClick={() => changeView('SIGNUP')}>{t('sign_in.register')}</a>
|
||||
</span>
|
||||
<span>
|
||||
{t('sign_in.already_have_an_account')}
|
||||
{' '}
|
||||
<a onClick={() => changeView('SIGNIN')}>
|
||||
{t('sign_in.sign_in')}
|
||||
</a>
|
||||
{t('sign_in.already_have_an_account')}{' '}
|
||||
<a onClick={() => changeView('SIGNIN')}>{t('sign_in.sign_in')}</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,27 +1,38 @@
|
||||
import React from 'react';
|
||||
import {Button, Spinner, Success, Alert} from 'plugin-api/beta/client/components/ui';
|
||||
import {
|
||||
Button,
|
||||
Spinner,
|
||||
Success,
|
||||
Alert,
|
||||
} from 'plugin-api/beta/client/components/ui';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './ResendVerification.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class ResendVerification extends React.Component {
|
||||
render() {
|
||||
const {resendVerification, error, loading, success, email} = this.props;
|
||||
const { resendVerification, error, loading, success, email } = this.props;
|
||||
return (
|
||||
<div className="talk-resend-verification">
|
||||
<h1 className={styles.header}>
|
||||
{t('sign_in.email_verify_cta')}
|
||||
</h1>
|
||||
<h1 className={styles.header}>{t('sign_in.email_verify_cta')}</h1>
|
||||
|
||||
{error &&
|
||||
{error && (
|
||||
<Alert>
|
||||
{error.translation_key ? t(`error.${error.translation_key}`) : error.toString()}
|
||||
</Alert>}
|
||||
{error.translation_key
|
||||
? t(`error.${error.translation_key}`)
|
||||
: error.toString()}
|
||||
</Alert>
|
||||
)}
|
||||
<div className={styles.notVerified}>
|
||||
{t('error.email_not_verified', email)}
|
||||
</div>
|
||||
<div>
|
||||
<Button id="resendConfirmEmail" cStyle="black" onClick={resendVerification} full>
|
||||
<Button
|
||||
id="resendConfirmEmail"
|
||||
cStyle="black"
|
||||
onClick={resendVerification}
|
||||
full
|
||||
>
|
||||
{t('sign_in.request_new_verify_email')}
|
||||
</Button>
|
||||
{loading && <Spinner />}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import {Dialog} from 'plugin-api/beta/client/components/ui';
|
||||
import { Dialog } from 'plugin-api/beta/client/components/ui';
|
||||
import styles from './styles.css';
|
||||
|
||||
import SignInContent from './SignInContent';
|
||||
@@ -7,20 +7,25 @@ import SignUpContent from './SignUpContent';
|
||||
import ForgotContent from './ForgotContent';
|
||||
import ResendVerification from './ResendVerification';
|
||||
|
||||
const SignDialog = ({open, view, resetSignInDialog, ...props}) => (
|
||||
const SignDialog = ({ open, view, resetSignInDialog, ...props }) => (
|
||||
<Dialog className={styles.dialog} id="signInDialog" open={open}>
|
||||
{view !== 'SIGNIN' && <span className={styles.close} onClick={resetSignInDialog}>×</span>}
|
||||
{view !== 'SIGNIN' && (
|
||||
<span className={styles.close} onClick={resetSignInDialog}>
|
||||
×
|
||||
</span>
|
||||
)}
|
||||
{view === 'SIGNIN' && <SignInContent {...props} />}
|
||||
{view === 'SIGNUP' && <SignUpContent {...props} />}
|
||||
{view === 'FORGOT' && <ForgotContent {...props} />}
|
||||
{view === 'RESEND_VERIFICATION' &&
|
||||
{view === 'RESEND_VERIFICATION' && (
|
||||
<ResendVerification
|
||||
resendVerification={props.resendVerification}
|
||||
error={props.auth.emailVerificationFailure}
|
||||
success={props.auth.emailVerificationSuccess}
|
||||
loading={props.auth.emailVerificationLoading}
|
||||
email={props.auth.email}
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import React from 'react';
|
||||
import {Button} from 'plugin-api/beta/client/components/ui';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {showSignInDialog} from 'coral-embed-stream/src/actions/auth';
|
||||
import { Button } from 'plugin-api/beta/client/components/ui';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { showSignInDialog } from 'coral-embed-stream/src/actions/auth';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const SignInButton = ({loggedIn, showSignInDialog}) => (
|
||||
const SignInButton = ({ loggedIn, showSignInDialog }) => (
|
||||
<div className="talk-stream-auth-sign-in-button">
|
||||
{!loggedIn
|
||||
? <Button id="coralSignInButton" onClick={showSignInDialog} full>
|
||||
{!loggedIn ? (
|
||||
<Button id="coralSignInButton" onClick={showSignInDialog} full>
|
||||
{t('sign_in.sign_in_to_comment')}
|
||||
</Button>
|
||||
: null}
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const mapStateToProps = ({auth}) => ({
|
||||
loggedIn: auth.loggedIn
|
||||
const mapStateToProps = ({ auth }) => ({
|
||||
loggedIn: auth.loggedIn,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({showSignInDialog}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ showSignInDialog }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SignInButton);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import { connect } from 'react-redux';
|
||||
import SignDialog from './SignDialog';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import errorMsj from 'coral-framework/helpers/error';
|
||||
import validate from 'coral-framework/helpers/validate';
|
||||
@@ -30,49 +30,48 @@ class SignInContainer extends React.Component {
|
||||
email: '',
|
||||
username: '',
|
||||
password: '',
|
||||
confirmPassword: ''
|
||||
confirmPassword: '',
|
||||
},
|
||||
errors: {},
|
||||
showErrors: false
|
||||
showErrors: false,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('storage', this.handleAuth);
|
||||
|
||||
const {formData} = this.state;
|
||||
const { formData } = this.state;
|
||||
const errors = Object.keys(formData).reduce((map, prop) => {
|
||||
map[prop] = t('sign_in.required_field');
|
||||
return map;
|
||||
}, {});
|
||||
this.setState({errors});
|
||||
this.setState({ errors });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('storage', this.handleAuth);
|
||||
}
|
||||
|
||||
handleAuth = (e) => {
|
||||
|
||||
handleAuth = e => {
|
||||
// Listening to FB changes
|
||||
// FB localStorage key is 'auth'
|
||||
const authCallback = this.props.facebookCallback;
|
||||
|
||||
if (e.key === 'auth') {
|
||||
const {err, data} = JSON.parse(e.newValue);
|
||||
const { err, data } = JSON.parse(e.newValue);
|
||||
authCallback(err, data);
|
||||
}
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
const {name, value} = e.target;
|
||||
handleChange = e => {
|
||||
const { name, value } = e.target;
|
||||
this.setState(
|
||||
(state) => ({
|
||||
state => ({
|
||||
...state,
|
||||
formData: {
|
||||
...state.formData,
|
||||
[name]: value
|
||||
}
|
||||
[name]: value,
|
||||
},
|
||||
}),
|
||||
() => {
|
||||
this.validation(name, value);
|
||||
@@ -81,29 +80,26 @@ class SignInContainer extends React.Component {
|
||||
};
|
||||
|
||||
resendVerification = () => {
|
||||
this.props
|
||||
.requestConfirmEmail(this.props.auth.email)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
// allow success UI to be shown for a second, and then close the modal
|
||||
this.props.resetSignInDialog();
|
||||
}, 2500);
|
||||
});
|
||||
this.props.requestConfirmEmail(this.props.auth.email).then(() => {
|
||||
setTimeout(() => {
|
||||
// allow success UI to be shown for a second, and then close the modal
|
||||
this.props.resetSignInDialog();
|
||||
}, 2500);
|
||||
});
|
||||
};
|
||||
|
||||
addError = (name, error) => {
|
||||
return this.setState((state) => ({
|
||||
return this.setState(state => ({
|
||||
errors: {
|
||||
...state.errors,
|
||||
[name]: error
|
||||
}
|
||||
[name]: error,
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
validation = (name, value) => {
|
||||
const {addError} = this;
|
||||
const {formData} = this.state;
|
||||
const { addError } = this;
|
||||
const { formData } = this.state;
|
||||
|
||||
if (!value.length) {
|
||||
addError(name, t('sign_in.required_field'));
|
||||
@@ -117,23 +113,23 @@ class SignInContainer extends React.Component {
|
||||
} else {
|
||||
const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line
|
||||
// Removes Error
|
||||
this.setState((state) => ({...state, errors}));
|
||||
this.setState(state => ({ ...state, errors }));
|
||||
}
|
||||
};
|
||||
|
||||
isCompleted = () => {
|
||||
const {formData} = this.state;
|
||||
return !Object.keys(formData).filter((prop) => !formData[prop].length).length;
|
||||
const { formData } = this.state;
|
||||
return !Object.keys(formData).filter(prop => !formData[prop].length).length;
|
||||
};
|
||||
|
||||
displayErrors = (show = true) => {
|
||||
this.setState({showErrors: show});
|
||||
this.setState({ showErrors: show });
|
||||
};
|
||||
|
||||
handleSignUp = (e) => {
|
||||
handleSignUp = e => {
|
||||
e.preventDefault();
|
||||
const {errors} = this.state;
|
||||
const {fetchSignUp, validForm, invalidForm} = this.props;
|
||||
const { errors } = this.state;
|
||||
const { fetchSignUp, validForm, invalidForm } = this.props;
|
||||
this.displayErrors();
|
||||
if (this.isCompleted() && !Object.keys(errors).length) {
|
||||
fetchSignUp(this.state.formData);
|
||||
@@ -143,14 +139,18 @@ class SignInContainer extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
handleSignIn = (e) => {
|
||||
handleSignIn = e => {
|
||||
e.preventDefault();
|
||||
this.props.fetchSignIn(this.state.formData);
|
||||
};
|
||||
|
||||
render() {
|
||||
const {auth} = this.props;
|
||||
const {requireEmailConfirmation, emailVerificationLoading, emailVerificationSuccess} = auth;
|
||||
const { auth } = this.props;
|
||||
const {
|
||||
requireEmailConfirmation,
|
||||
emailVerificationLoading,
|
||||
emailVerificationSuccess,
|
||||
} = auth;
|
||||
|
||||
return (
|
||||
<SignDialog
|
||||
@@ -167,11 +167,11 @@ class SignInContainer extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth
|
||||
const mapStateToProps = state => ({
|
||||
auth: state.auth,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
facebookCallback,
|
||||
@@ -185,7 +185,7 @@ const mapDispatchToProps = (dispatch) =>
|
||||
hideSignInDialog,
|
||||
resetSignInDialog,
|
||||
invalidForm,
|
||||
validForm
|
||||
validForm,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Button, TextField, Spinner, Alert} from 'plugin-api/beta/client/components/ui';
|
||||
import {
|
||||
Button,
|
||||
TextField,
|
||||
Spinner,
|
||||
Alert,
|
||||
} from 'plugin-api/beta/client/components/ui';
|
||||
import styles from './styles.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
@@ -10,19 +15,20 @@ const SignInContent = ({
|
||||
changeView,
|
||||
handleSignIn,
|
||||
auth,
|
||||
fetchSignInFacebook
|
||||
fetchSignInFacebook,
|
||||
}) => {
|
||||
return (
|
||||
<div className="coral-sign-in">
|
||||
<div className={`${styles.header} header`}>
|
||||
<h1>
|
||||
{t('sign_in.sign_in_to_join')}
|
||||
</h1>
|
||||
<h1>{t('sign_in.sign_in_to_join')}</h1>
|
||||
</div>
|
||||
{auth.error &&
|
||||
{auth.error && (
|
||||
<Alert>
|
||||
{auth.error.translation_key ? t(`error.${auth.error.translation_key}`) : auth.error.toString()}
|
||||
</Alert>}
|
||||
{auth.error.translation_key
|
||||
? t(`error.${auth.error.translation_key}`)
|
||||
: auth.error.toString()}
|
||||
</Alert>
|
||||
)}
|
||||
<div>
|
||||
<div className={`${styles.socialConnections} social-connections`}>
|
||||
<Button cStyle="facebook" onClick={fetchSignInFacebook} full>
|
||||
@@ -30,9 +36,7 @@ const SignInContent = ({
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.separator}>
|
||||
<h1>
|
||||
{t('sign_in.or')}
|
||||
</h1>
|
||||
<h1>{t('sign_in.or')}</h1>
|
||||
</div>
|
||||
<form onSubmit={handleSignIn}>
|
||||
<TextField
|
||||
@@ -40,7 +44,7 @@ const SignInContent = ({
|
||||
type="email"
|
||||
label={t('sign_in.email')}
|
||||
value={formData.email}
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<TextField
|
||||
@@ -48,12 +52,12 @@ const SignInContent = ({
|
||||
type="password"
|
||||
label={t('sign_in.password')}
|
||||
value={formData.password}
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className={styles.action}>
|
||||
{!auth.isLoading
|
||||
? <Button
|
||||
{!auth.isLoading ? (
|
||||
<Button
|
||||
id="coralLogInButton"
|
||||
type="submit"
|
||||
cStyle="black"
|
||||
@@ -62,7 +66,9 @@ const SignInContent = ({
|
||||
>
|
||||
{t('sign_in.sign_in')}
|
||||
</Button>
|
||||
: <Spinner />}
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -87,7 +93,7 @@ SignInContent.propTypes = {
|
||||
auth: PropTypes.shape({
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
error: PropTypes.string,
|
||||
emailVerificationFailure: PropTypes.bool
|
||||
emailVerificationFailure: PropTypes.bool,
|
||||
}).isRequired,
|
||||
fetchSignInFacebook: PropTypes.func.isRequired,
|
||||
handleSignIn: PropTypes.func.isRequired,
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import styles from './styles.css';
|
||||
import React from 'react';
|
||||
import {Button, TextField, Spinner, Success, Alert} from 'plugin-api/beta/client/components/ui';
|
||||
import {
|
||||
Button,
|
||||
TextField,
|
||||
Spinner,
|
||||
Success,
|
||||
Alert,
|
||||
} from 'plugin-api/beta/client/components/ui';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
class SignUpContent extends React.Component {
|
||||
|
||||
componentWillReceiveProps(next) {
|
||||
if (
|
||||
!this.props.emailVerificationEnabled &&
|
||||
@@ -27,19 +32,17 @@ class SignUpContent extends React.Component {
|
||||
showErrors,
|
||||
changeView,
|
||||
handleSignUp,
|
||||
fetchSignUpFacebook
|
||||
fetchSignUpFacebook,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
<h1>
|
||||
{t('sign_in.sign_up')}
|
||||
</h1>
|
||||
<h1>{t('sign_in.sign_up')}</h1>
|
||||
</div>
|
||||
|
||||
{auth.error && <Alert>{auth.error}</Alert>}
|
||||
{!auth.successSignUp &&
|
||||
{!auth.successSignUp && (
|
||||
<div>
|
||||
<div className={styles.socialConnections}>
|
||||
<Button cStyle="facebook" onClick={fetchSignUpFacebook} full>
|
||||
@@ -47,9 +50,7 @@ class SignUpContent extends React.Component {
|
||||
</Button>
|
||||
</div>
|
||||
<div className={styles.separator}>
|
||||
<h1>
|
||||
{t('sign_in.or')}
|
||||
</h1>
|
||||
<h1>{t('sign_in.or')}</h1>
|
||||
</div>
|
||||
<form onSubmit={handleSignUp}>
|
||||
<TextField
|
||||
@@ -57,7 +58,7 @@ class SignUpContent extends React.Component {
|
||||
type="email"
|
||||
label={t('sign_in.email')}
|
||||
value={formData.email}
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
showErrors={showErrors}
|
||||
errorMsg={errors.email}
|
||||
onChange={handleChange}
|
||||
@@ -68,7 +69,7 @@ class SignUpContent extends React.Component {
|
||||
label={t('sign_in.username')}
|
||||
value={formData.username}
|
||||
showErrors={showErrors}
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
errorMsg={errors.username}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
@@ -78,21 +79,23 @@ class SignUpContent extends React.Component {
|
||||
label={t('sign_in.password')}
|
||||
value={formData.password}
|
||||
showErrors={showErrors}
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
errorMsg={errors.password}
|
||||
onChange={handleChange}
|
||||
minLength="8"
|
||||
/>
|
||||
{errors.password &&
|
||||
{errors.password && (
|
||||
<span className={styles.hint}>
|
||||
{' '}Password must be at least 8 characters.{' '}
|
||||
</span>}
|
||||
{' '}
|
||||
Password must be at least 8 characters.{' '}
|
||||
</span>
|
||||
)}
|
||||
<TextField
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
label={t('sign_in.confirm_password')}
|
||||
value={formData.confirmPassword}
|
||||
style={{fontSize: 16}}
|
||||
style={{ fontSize: 16 }}
|
||||
showErrors={showErrors}
|
||||
errorMsg={errors.confirmPassword}
|
||||
onChange={handleChange}
|
||||
@@ -111,23 +114,24 @@ class SignUpContent extends React.Component {
|
||||
{auth.isLoading && <Spinner />}
|
||||
</div>
|
||||
</form>
|
||||
</div>}
|
||||
{auth.successSignUp &&
|
||||
</div>
|
||||
)}
|
||||
{auth.successSignUp && (
|
||||
<div>
|
||||
<Success />
|
||||
{emailVerificationEnabled &&
|
||||
{emailVerificationEnabled && (
|
||||
<p>
|
||||
{t('sign_in.verify_email')}
|
||||
<br />
|
||||
<br />
|
||||
{t('sign_in.verify_email2')}
|
||||
</p>}
|
||||
</div>}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.footer}>
|
||||
{t('sign_in.already_have_an_account')} <a
|
||||
id="coralSignInViewTrigger"
|
||||
onClick={() => changeView('SIGNIN')}
|
||||
>
|
||||
{t('sign_in.already_have_an_account')}{' '}
|
||||
<a id="coralSignInViewTrigger" onClick={() => changeView('SIGNIN')}>
|
||||
{t('sign_in.sign_in')}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import {logout} from 'coral-embed-stream/src/actions/auth';
|
||||
import { logout } from 'coral-embed-stream/src/actions/auth';
|
||||
|
||||
const UserBox = ({loggedIn, user, logout, onShowProfile}) => (
|
||||
const UserBox = ({ loggedIn, user, logout, onShowProfile }) => (
|
||||
<div>
|
||||
{
|
||||
loggedIn ? (
|
||||
<div className={`${styles.userBox} talk-stream-auth-userbox`}>
|
||||
<span className={styles.userBoxLoggedIn}>{t('sign_in.logged_in_as')}</span>
|
||||
<a onClick={onShowProfile}>{user.username}</a>. {t('sign_in.not_you')}
|
||||
<a className={`${styles.logout} talk-stream-userbox-logout`} onClick={() => logout()}>
|
||||
{t('sign_in.logout')}
|
||||
</a>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{loggedIn ? (
|
||||
<div className={`${styles.userBox} talk-stream-auth-userbox`}>
|
||||
<span className={styles.userBoxLoggedIn}>
|
||||
{t('sign_in.logged_in_as')}
|
||||
</span>
|
||||
<a onClick={onShowProfile}>{user.username}</a>. {t('sign_in.not_you')}
|
||||
<a
|
||||
className={`${styles.logout} talk-stream-userbox-logout`}
|
||||
onClick={() => logout()}
|
||||
>
|
||||
{t('sign_in.logout')}
|
||||
</a>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
const mapStateToProps = ({auth}) => ({
|
||||
const mapStateToProps = ({ auth }) => ({
|
||||
loggedIn: auth.loggedIn,
|
||||
user: auth.user
|
||||
user: auth.user,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({logout}, dispatch);
|
||||
const mapDispatchToProps = dispatch => bindActionCreators({ logout }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserBox);
|
||||
|
||||
@@ -8,7 +8,6 @@ export default {
|
||||
translations,
|
||||
slots: {
|
||||
stream: [UserBox, SignInButton, ChangeUserNameContainer],
|
||||
login: [SignInContainer]
|
||||
}
|
||||
login: [SignInContainer],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
module.exports = {};
|
||||
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import {SET_CONTENT_SLOT, RESET_CONTENT_SLOT, OPEN_MENU, CLOSE_MENU} from './constants';
|
||||
import {
|
||||
SET_CONTENT_SLOT,
|
||||
RESET_CONTENT_SLOT,
|
||||
OPEN_MENU,
|
||||
CLOSE_MENU,
|
||||
} from './constants';
|
||||
|
||||
export const setContentSlot = (slot) => ({
|
||||
export const setContentSlot = slot => ({
|
||||
type: SET_CONTENT_SLOT,
|
||||
slot,
|
||||
});
|
||||
@@ -9,7 +14,7 @@ export const resetContentSlot = () => ({
|
||||
type: RESET_CONTENT_SLOT,
|
||||
});
|
||||
|
||||
export const openMenu = (id) => ({
|
||||
export const openMenu = id => ({
|
||||
type: OPEN_MENU,
|
||||
id,
|
||||
});
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import React from 'react';
|
||||
import Menu from './Menu';
|
||||
import styles from './AuthorName.css';
|
||||
import {ClickOutside} from 'plugin-api/beta/client/components';
|
||||
import { ClickOutside } from 'plugin-api/beta/client/components';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({data, root, asset, comment, contentSlot, menuVisible, toggleMenu, hideMenu}) => {
|
||||
export default ({
|
||||
data,
|
||||
root,
|
||||
asset,
|
||||
comment,
|
||||
contentSlot,
|
||||
menuVisible,
|
||||
toggleMenu,
|
||||
hideMenu,
|
||||
}) => {
|
||||
return (
|
||||
<ClickOutside onClickOutside={hideMenu}>
|
||||
<div className={cn(styles.root, 'talk-plugin-author-menu')}>
|
||||
@@ -12,11 +21,9 @@ export default ({data, root, asset, comment, contentSlot, menuVisible, toggleMen
|
||||
className={cn(styles.button, 'talk-plugin-author-menu-button')}
|
||||
onClick={toggleMenu}
|
||||
>
|
||||
<span className={styles.name}>
|
||||
{comment.user.username}
|
||||
</span>
|
||||
<span className={styles.name}>{comment.user.username}</span>
|
||||
</button>
|
||||
{menuVisible &&
|
||||
{menuVisible && (
|
||||
<Menu
|
||||
data={data}
|
||||
root={root}
|
||||
@@ -24,7 +31,7 @@ export default ({data, root, asset, comment, contentSlot, menuVisible, toggleMen
|
||||
comment={comment}
|
||||
contentSlot={contentSlot}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</ClickOutside>
|
||||
);
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React from 'react';
|
||||
import styles from './Menu.css';
|
||||
import {Slot} from 'plugin-api/beta/client/components';
|
||||
import { Slot } from 'plugin-api/beta/client/components';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({data, root, asset, comment, contentSlot}) => {
|
||||
export default ({ data, root, asset, comment, contentSlot }) => {
|
||||
if (contentSlot) {
|
||||
return (
|
||||
<div className={cn(styles.menu, 'talk-plugin-author-menu-popup')}>
|
||||
<Slot
|
||||
fill={contentSlot}
|
||||
data={data}
|
||||
queryData={{asset, root, comment}}
|
||||
queryData={{ asset, root, comment }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -22,13 +22,13 @@ export default ({data, root, asset, comment, contentSlot}) => {
|
||||
className={cn('talk-plugin-author-menu-infos')}
|
||||
fill={'authorMenuInfos'}
|
||||
data={data}
|
||||
queryData={{asset, root, comment}}
|
||||
queryData={{ asset, root, comment }}
|
||||
/>
|
||||
<Slot
|
||||
className={cn(styles.actions, 'talk-plugin-author-menu-actions')}
|
||||
fill={'authorMenuActions'}
|
||||
data={data}
|
||||
queryData={{asset, root, comment}}
|
||||
queryData={{ asset, root, comment }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,4 +4,3 @@ export const SET_CONTENT_SLOT = `${prefix}_SET_CONTENT_SLOT`;
|
||||
export const RESET_CONTENT_SLOT = `${prefix}_RESET_CONTENT_SLOT`;
|
||||
export const OPEN_MENU = `${prefix}_OPEN_MENU`;
|
||||
export const CLOSE_MENU = `${prefix}_CLOSE_MENU`;
|
||||
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import React from 'react';
|
||||
import {connect, withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import { connect, withFragments } from 'plugin-api/beta/client/hocs';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import AuthorName from '../components/AuthorName';
|
||||
import {setContentSlot, resetContentSlot, openMenu, closeMenu} from '../actions';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {getSlotFragmentSpreads, getShallowChanges} from 'plugin-api/beta/client/utils';
|
||||
import {
|
||||
setContentSlot,
|
||||
resetContentSlot,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
} from '../actions';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import {
|
||||
getSlotFragmentSpreads,
|
||||
getShallowChanges,
|
||||
} from 'plugin-api/beta/client/utils';
|
||||
|
||||
class AuthorNameContainer extends React.Component {
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
|
||||
// Specifically handle `showMenuForComment` if it is the only change.
|
||||
const changes = getShallowChanges(this.props, nextProps);
|
||||
if (changes.length === 1 && changes[0] === 'showMenuForComment') {
|
||||
@@ -32,45 +38,47 @@ class AuthorNameContainer extends React.Component {
|
||||
} else {
|
||||
this.props.openMenu(this.props.comment.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
hideMenu = () => {
|
||||
if (this.props.showMenuForComment === this.props.comment.id) {
|
||||
this.props.closeMenu();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <AuthorName
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
comment={this.props.comment}
|
||||
contentSlot={this.props.contentSlot}
|
||||
menuVisible={this.props.showMenuForComment === this.props.comment.id}
|
||||
toggleMenu={this.toggleMenu}
|
||||
hideMenu={this.hideMenu}
|
||||
/>;
|
||||
return (
|
||||
<AuthorName
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
comment={this.props.comment}
|
||||
contentSlot={this.props.contentSlot}
|
||||
menuVisible={this.props.showMenuForComment === this.props.comment.id}
|
||||
toggleMenu={this.toggleMenu}
|
||||
hideMenu={this.hideMenu}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const slots = [
|
||||
'authorMenuInfos',
|
||||
'authorMenuActions',
|
||||
];
|
||||
const slots = ['authorMenuInfos', 'authorMenuActions'];
|
||||
|
||||
const mapStateToProps = ({talkPluginAuthorMenu: state}) => ({
|
||||
const mapStateToProps = ({ talkPluginAuthorMenu: state }) => ({
|
||||
contentSlot: state.contentSlot,
|
||||
showMenuForComment: state.showMenuForComment,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
setContentSlot,
|
||||
resetContentSlot,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
setContentSlot,
|
||||
resetContentSlot,
|
||||
openMenu,
|
||||
closeMenu,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const withAuthorNameFragments = withFragments({
|
||||
root: gql`
|
||||
@@ -96,7 +104,7 @@ const withAuthorNameFragments = withFragments({
|
||||
|
||||
const enhance = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withAuthorNameFragments,
|
||||
withAuthorNameFragments
|
||||
);
|
||||
|
||||
export default enhance(AuthorNameContainer);
|
||||
|
||||
@@ -5,7 +5,7 @@ import translations from './translations.yml';
|
||||
export default {
|
||||
reducer,
|
||||
slots: {
|
||||
commentAuthorName: [AuthorName]
|
||||
commentAuthorName: [AuthorName],
|
||||
},
|
||||
translations
|
||||
translations,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import {SET_CONTENT_SLOT, RESET_CONTENT_SLOT, OPEN_MENU, CLOSE_MENU} from './constants';
|
||||
import {
|
||||
SET_CONTENT_SLOT,
|
||||
RESET_CONTENT_SLOT,
|
||||
OPEN_MENU,
|
||||
CLOSE_MENU,
|
||||
} from './constants';
|
||||
|
||||
const initialState = {
|
||||
contentSlot: null,
|
||||
@@ -7,28 +12,28 @@ const initialState = {
|
||||
|
||||
export default function reducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case SET_CONTENT_SLOT:
|
||||
return {
|
||||
...state,
|
||||
contentSlot: action.slot,
|
||||
};
|
||||
case RESET_CONTENT_SLOT:
|
||||
return {
|
||||
...state,
|
||||
contentSlot: initialState.contentSlot,
|
||||
};
|
||||
case OPEN_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: action.id,
|
||||
};
|
||||
case CLOSE_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: null,
|
||||
contentSlot: null,
|
||||
};
|
||||
default :
|
||||
return state;
|
||||
case SET_CONTENT_SLOT:
|
||||
return {
|
||||
...state,
|
||||
contentSlot: action.slot,
|
||||
};
|
||||
case RESET_CONTENT_SLOT:
|
||||
return {
|
||||
...state,
|
||||
contentSlot: initialState.contentSlot,
|
||||
};
|
||||
case OPEN_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: action.id,
|
||||
};
|
||||
case CLOSE_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: null,
|
||||
contentSlot: null,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,22 @@ import Linkify from 'react-linkify';
|
||||
|
||||
const name = 'talk-plugin-comment-content';
|
||||
|
||||
const CommentContent = ({comment}) => {
|
||||
const CommentContent = ({ comment }) => {
|
||||
const textbreaks = comment.body.split('\n');
|
||||
return <span className={`${name}-text`}>
|
||||
{
|
||||
textbreaks.map((line, i) => {
|
||||
return (
|
||||
<span className={`${name}-text`}>
|
||||
{textbreaks.map((line, i) => {
|
||||
return (
|
||||
<span key={i} className={`${name}-line`}>
|
||||
<Linkify properties={{target: '_blank'}}>
|
||||
{line.trim()}
|
||||
</Linkify>
|
||||
{i !== textbreaks.length - 1 && <br className={`${name}-linebreak`}/>}
|
||||
<Linkify properties={{ target: '_blank' }}>{line.trim()}</Linkify>
|
||||
{i !== textbreaks.length - 1 && (
|
||||
<br className={`${name}-linebreak`} />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
</span>;
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommentContent;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import {withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import { gql } from 'react-apollo';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
import CommentContent from '../components/CommentContent';
|
||||
|
||||
export default withFragments({
|
||||
comment: gql`
|
||||
fragment TalkPluginCommentContent_comment on Comment {
|
||||
body
|
||||
}`
|
||||
}
|
||||
`,
|
||||
})(CommentContent);
|
||||
|
||||
@@ -2,6 +2,6 @@ import CommentContent from './containers/CommentContent';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
commentContent: [CommentContent]
|
||||
}
|
||||
commentContent: [CommentContent],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,25 +3,28 @@ const DataLoader = require('dataloader');
|
||||
|
||||
const CommentModel = require('../../models/comment');
|
||||
|
||||
console.warn('Enabling the talk-plugin-deep-reply-count plugin introduces a signifigant performance impact on larger sites, use with care.');
|
||||
console.warn(
|
||||
'Enabling the talk-plugin-deep-reply-count plugin introduces a signifigant performance impact on larger sites, use with care.'
|
||||
);
|
||||
|
||||
// genDeepCommentCount will return the deep comment count for a given parent id.
|
||||
const genDeepCommentCount = async (context, parent_ids) => {
|
||||
|
||||
// Get all the replies to the parent comments.
|
||||
const replies = await CommentModel
|
||||
.find({
|
||||
const replies = await CommentModel.find(
|
||||
{
|
||||
parent_id: {
|
||||
$in: _.uniq(parent_ids),
|
||||
},
|
||||
}, {
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
reply_count: 1,
|
||||
parent_id: 1,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Get all the replies that have comments on them.
|
||||
const commentedOnReplies = replies.filter(({reply_count}) => {
|
||||
const commentedOnReplies = replies.filter(({ reply_count }) => {
|
||||
return reply_count && reply_count > 0;
|
||||
});
|
||||
|
||||
@@ -29,27 +32,34 @@ const genDeepCommentCount = async (context, parent_ids) => {
|
||||
|
||||
// And if there were any..
|
||||
if (commentedOnReplies.length > 0) {
|
||||
|
||||
// Load the reply count for each of them.
|
||||
deepReplyCount = await context.loaders.Comments.getDeepCount.loadMany(_.uniq(commentedOnReplies.map(({id}) => {
|
||||
return id;
|
||||
})));
|
||||
deepReplyCount = await context.loaders.Comments.getDeepCount.loadMany(
|
||||
_.uniq(
|
||||
commentedOnReplies.map(({ id }) => {
|
||||
return id;
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Get all the direct replies to the parent comments.
|
||||
const allDirectReplies = _.groupBy(replies, 'parent_id');
|
||||
|
||||
// Collect all the ancestor replies.
|
||||
const allAncestorReplies = _.groupBy(_.zip(commentedOnReplies, deepReplyCount), ([{parent_id}]) => {
|
||||
return parent_id;
|
||||
});
|
||||
const allAncestorReplies = _.groupBy(
|
||||
_.zip(commentedOnReplies, deepReplyCount),
|
||||
([{ parent_id }]) => {
|
||||
return parent_id;
|
||||
}
|
||||
);
|
||||
|
||||
// Return the replies in an array matching that of the input parent_ids array.
|
||||
return parent_ids.map((parent_id) => {
|
||||
|
||||
return parent_ids.map(parent_id => {
|
||||
// Get the direct replies to this comment.
|
||||
const directReplies = parent_id in allDirectReplies ? allDirectReplies[parent_id] : [];
|
||||
const ancestorReplies = parent_id in allAncestorReplies ? allAncestorReplies[parent_id] : [];
|
||||
const directReplies =
|
||||
parent_id in allDirectReplies ? allDirectReplies[parent_id] : [];
|
||||
const ancestorReplies =
|
||||
parent_id in allAncestorReplies ? allAncestorReplies[parent_id] : [];
|
||||
|
||||
// Reduce this array.
|
||||
return ancestorReplies.reduce((acc, [, count]) => {
|
||||
@@ -66,16 +76,18 @@ module.exports = {
|
||||
deepReplyCount: Int
|
||||
}
|
||||
`,
|
||||
loaders: (context) => ({
|
||||
loaders: context => ({
|
||||
Comments: {
|
||||
getDeepCount: new DataLoader((parent_ids) => genDeepCommentCount(context, parent_ids)),
|
||||
}
|
||||
getDeepCount: new DataLoader(parent_ids =>
|
||||
genDeepCommentCount(context, parent_ids)
|
||||
),
|
||||
},
|
||||
}),
|
||||
resolvers: {
|
||||
Comment: {
|
||||
deepReplyCount({id}, args, {loaders: {Comments}}) {
|
||||
deepReplyCount({ id }, args, { loaders: { Comments } }) {
|
||||
return Comments.getDeepCount.load(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,5 +3,5 @@ const router = require('./server/router');
|
||||
|
||||
module.exports = {
|
||||
passport,
|
||||
router
|
||||
router,
|
||||
};
|
||||
|
||||
@@ -1,34 +1,42 @@
|
||||
const FacebookStrategy = require('passport-facebook').Strategy;
|
||||
const UsersService = require('services/users');
|
||||
const {ValidateUserLogin} = require('services/passport');
|
||||
let {
|
||||
ROOT_URL
|
||||
} = require('config');
|
||||
const { ValidateUserLogin } = require('services/passport');
|
||||
let { ROOT_URL } = require('config');
|
||||
|
||||
if (ROOT_URL[ROOT_URL.length - 1] !== '/') {
|
||||
ROOT_URL += '/';
|
||||
}
|
||||
|
||||
module.exports = (passport) => {
|
||||
if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && process.env.TALK_ROOT_URL) {
|
||||
passport.use(new FacebookStrategy({
|
||||
clientID: process.env.TALK_FACEBOOK_APP_ID,
|
||||
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
|
||||
callbackURL: `${ROOT_URL}api/v1/auth/facebook/callback`,
|
||||
passReqToCallback: true,
|
||||
profileFields: ['id', 'displayName', 'picture.type(large)']
|
||||
}, async (req, accessToken, refreshToken, profile, done) => {
|
||||
module.exports = passport => {
|
||||
if (
|
||||
process.env.TALK_FACEBOOK_APP_ID &&
|
||||
process.env.TALK_FACEBOOK_APP_SECRET &&
|
||||
process.env.TALK_ROOT_URL
|
||||
) {
|
||||
passport.use(
|
||||
new FacebookStrategy(
|
||||
{
|
||||
clientID: process.env.TALK_FACEBOOK_APP_ID,
|
||||
clientSecret: process.env.TALK_FACEBOOK_APP_SECRET,
|
||||
callbackURL: `${ROOT_URL}api/v1/auth/facebook/callback`,
|
||||
passReqToCallback: true,
|
||||
profileFields: ['id', 'displayName', 'picture.type(large)'],
|
||||
},
|
||||
async (req, accessToken, refreshToken, profile, done) => {
|
||||
let user;
|
||||
try {
|
||||
user = await UsersService.findOrCreateExternalUser(profile);
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
let user;
|
||||
try {
|
||||
user = await UsersService.findOrCreateExternalUser(profile);
|
||||
} catch (err) {
|
||||
return done(err);
|
||||
}
|
||||
|
||||
return ValidateUserLogin(profile, user, done);
|
||||
}));
|
||||
return ValidateUserLogin(profile, user, done);
|
||||
}
|
||||
)
|
||||
);
|
||||
} else if (process.env.NODE_ENV !== 'test') {
|
||||
throw new Error('Facebook cannot be enabled, missing one of TALK_FACEBOOK_APP_ID, TALK_FACEBOOK_APP_SECRET, TALK_ROOT_URL');
|
||||
throw new Error(
|
||||
'Facebook cannot be enabled, missing one of TALK_FACEBOOK_APP_ID, TALK_FACEBOOK_APP_SECRET, TALK_ROOT_URL'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,20 +1,29 @@
|
||||
module.exports = (router) => {
|
||||
|
||||
const {passport, HandleAuthPopupCallback} = require('services/passport');
|
||||
module.exports = router => {
|
||||
const { passport, HandleAuthPopupCallback } = require('services/passport');
|
||||
|
||||
/**
|
||||
* Facebook auth endpoint, this will redirect the user immediatly to facebook
|
||||
* for authorization.
|
||||
*/
|
||||
router.get('/api/v1/auth/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']}));
|
||||
router.get(
|
||||
'/api/v1/auth/facebook',
|
||||
passport.authenticate('facebook', {
|
||||
display: 'popup',
|
||||
authType: 'rerequest',
|
||||
scope: ['public_profile'],
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Facebook callback endpoint, this will send the user a html page designed to
|
||||
* send back the user credentials upon sucesfull login.
|
||||
*/
|
||||
router.get('/api/v1/auth/facebook/callback', (req, res, next) => {
|
||||
|
||||
// Perform the facebook login flow and pass the data back through the opener.
|
||||
passport.authenticate('facebook', {session: false}, HandleAuthPopupCallback(req, res, next))(req, res, next);
|
||||
passport.authenticate(
|
||||
'facebook',
|
||||
{ session: false },
|
||||
HandleAuthPopupCallback(req, res, next)
|
||||
)(req, res, next);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {OPEN_FEATURED_DIALOG, CLOSE_FEATURED_DIALOG} from './constants';
|
||||
import { OPEN_FEATURED_DIALOG, CLOSE_FEATURED_DIALOG } from './constants';
|
||||
|
||||
export const openFeaturedDialog = (comment, asset) => ({
|
||||
type: OPEN_FEATURED_DIALOG,
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './Comment.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Slot, CommentAuthorName, CommentTimestamp, CommentContent} from 'plugin-api/beta/client/components';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import {pluginName} from '../../package.json';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import {
|
||||
Slot,
|
||||
CommentAuthorName,
|
||||
CommentTimestamp,
|
||||
CommentContent,
|
||||
} from 'plugin-api/beta/client/components';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import { pluginName } from '../../package.json';
|
||||
import FeaturedButton from '../containers/FeaturedButton';
|
||||
|
||||
class Comment extends React.Component {
|
||||
|
||||
viewComment = () => {
|
||||
this.props.viewComment(this.props.comment.id);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {comment, asset, root, data} = this.props;
|
||||
const queryData = {comment, asset, root};
|
||||
const { comment, asset, root, data } = this.props;
|
||||
const queryData = { comment, asset, root };
|
||||
return (
|
||||
<div className={cn(styles.root, `${pluginName}-comment`)}>
|
||||
|
||||
<Slot
|
||||
component={'blockquote'}
|
||||
className={cn(styles.quote, `${pluginName}-comment-body`)}
|
||||
@@ -29,7 +32,6 @@ class Comment extends React.Component {
|
||||
/>
|
||||
|
||||
<div className={cn(`${pluginName}-comment-username-box`)}>
|
||||
|
||||
<Slot
|
||||
className={cn(styles.username, `${pluginName}-comment-username`)}
|
||||
fill="commentAuthorName"
|
||||
@@ -51,8 +53,13 @@ class Comment extends React.Component {
|
||||
</div>
|
||||
|
||||
<footer className={cn(styles.footer, `${pluginName}-comment-footer`)}>
|
||||
<div className={cn('talk-embed-stream-comment-actions-container-left', styles.reactionsContainer, `${pluginName}-comment-reactions`)}>
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'talk-embed-stream-comment-actions-container-left',
|
||||
styles.reactionsContainer,
|
||||
`${pluginName}-comment-reactions`
|
||||
)}
|
||||
>
|
||||
<Slot
|
||||
fill="commentReactions"
|
||||
root={root}
|
||||
@@ -69,9 +76,21 @@ class Comment extends React.Component {
|
||||
asset={asset}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn('talk-embed-stream-comment-actions-container-right', styles.actionsContainer, `${pluginName}-comment-actions`)}>
|
||||
<button className={cn(styles.goTo, `${pluginName}-comment-go-to`)} onClick={this.viewComment}>
|
||||
<Icon name="forum" className={styles.repliesIcon} /> {comment.replyCount} | {t('talk-plugin-featured-comments.go_to_conversation')} <Icon name="keyboard_arrow_right" className={styles.goToIcon} />
|
||||
<div
|
||||
className={cn(
|
||||
'talk-embed-stream-comment-actions-container-right',
|
||||
styles.actionsContainer,
|
||||
`${pluginName}-comment-actions`
|
||||
)}
|
||||
>
|
||||
<button
|
||||
className={cn(styles.goTo, `${pluginName}-comment-go-to`)}
|
||||
onClick={this.viewComment}
|
||||
>
|
||||
<Icon name="forum" className={styles.repliesIcon} />{' '}
|
||||
{comment.replyCount} |{' '}
|
||||
{t('talk-plugin-featured-comments.go_to_conversation')}{' '}
|
||||
<Icon name="keyboard_arrow_right" className={styles.goToIcon} />
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,25 +1,29 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './FeaturedButton.css';
|
||||
import {pluginName} from '../../package.json';
|
||||
import {can} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { pluginName } from '../../package.json';
|
||||
import { can } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
const FeaturedButton = (props) => {
|
||||
const {alreadyTagged, deleteTag, postTag, user} = props;
|
||||
const FeaturedButton = props => {
|
||||
const { alreadyTagged, deleteTag, postTag, user } = props;
|
||||
|
||||
return can(user, 'MODERATE_COMMENTS') ? (
|
||||
<button
|
||||
className={cn([`${pluginName}-tag-button`, styles.button, {[styles.featured] : alreadyTagged}])}
|
||||
onClick={alreadyTagged ? deleteTag : postTag} >
|
||||
|
||||
{alreadyTagged ?
|
||||
<Icon name="star" className={styles.icon} /> :
|
||||
className={cn([
|
||||
`${pluginName}-tag-button`,
|
||||
styles.button,
|
||||
{ [styles.featured]: alreadyTagged },
|
||||
])}
|
||||
onClick={alreadyTagged ? deleteTag : postTag}
|
||||
>
|
||||
{alreadyTagged ? (
|
||||
<Icon name="star" className={styles.icon} />
|
||||
) : (
|
||||
<Icon name="star_border" className={styles.icon} />
|
||||
}
|
||||
|
||||
)}
|
||||
</button>
|
||||
) : null ;
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default FeaturedButton;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Dialog} from 'coral-ui';
|
||||
import { Dialog } from 'coral-ui';
|
||||
import styles from './FeaturedDialog.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import Button from 'coral-ui/components/Button';
|
||||
|
||||
const FeaturedDialog = ({showFeaturedDialog, closeFeaturedDialog, postTag}) => {
|
||||
|
||||
const FeaturedDialog = ({
|
||||
showFeaturedDialog,
|
||||
closeFeaturedDialog,
|
||||
postTag,
|
||||
}) => {
|
||||
const onPerform = async () => {
|
||||
await postTag();
|
||||
await closeFeaturedDialog();
|
||||
@@ -18,9 +21,14 @@ const FeaturedDialog = ({showFeaturedDialog, closeFeaturedDialog, postTag}) => {
|
||||
className={cn(styles.dialog, 'talk-featured-dialog')}
|
||||
id="talkFeaturedDialog"
|
||||
open={showFeaturedDialog}
|
||||
onCancel={closeFeaturedDialog} >
|
||||
<span className={styles.close} onClick={closeFeaturedDialog}>×</span>
|
||||
<h2 className={styles.header}>{t('talk-plugin-featured-comments.feature_comment')}</h2>
|
||||
onCancel={closeFeaturedDialog}
|
||||
>
|
||||
<span className={styles.close} onClick={closeFeaturedDialog}>
|
||||
×
|
||||
</span>
|
||||
<h2 className={styles.header}>
|
||||
{t('talk-plugin-featured-comments.feature_comment')}
|
||||
</h2>
|
||||
<div className={styles.content}>
|
||||
{t('talk-plugin-featured-comments.are_you_sure')}
|
||||
</div>
|
||||
@@ -29,14 +37,16 @@ const FeaturedDialog = ({showFeaturedDialog, closeFeaturedDialog, postTag}) => {
|
||||
className={cn(styles.cancel, 'talk-featured-dialog-button-cancel')}
|
||||
cStyle="cancel"
|
||||
onClick={closeFeaturedDialog}
|
||||
raised >
|
||||
raised
|
||||
>
|
||||
{t('talk-plugin-featured-comments.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
<Button
|
||||
className={cn(styles.perform, 'talk-featured-dialog-button-confirm')}
|
||||
cStyle="black"
|
||||
onClick={onPerform}
|
||||
raised >
|
||||
raised
|
||||
>
|
||||
{t('talk-plugin-featured-comments.yes_feature_comment')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import styles from './InfoIcon.css';
|
||||
import cn from 'classnames';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
export default ({hover}) => (
|
||||
export default ({ hover }) => (
|
||||
<Icon
|
||||
name="info_outline"
|
||||
className={cn(styles.infoIcon, {[styles.on]: hover})}
|
||||
className={cn(styles.infoIcon, { [styles.on]: hover })}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Button} from 'coral-ui';
|
||||
import { Button } from 'coral-ui';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import cn from 'classnames';
|
||||
|
||||
class LoadMore extends React.Component {
|
||||
render () {
|
||||
const {loadingState, loadMore} = this.props;
|
||||
render() {
|
||||
const { loadingState, loadMore } = this.props;
|
||||
const disabled = loadingState === 'loading';
|
||||
return (
|
||||
<div className='talk-load-more'>
|
||||
<div className="talk-load-more">
|
||||
<Button
|
||||
onClick={loadMore}
|
||||
className={cn('talk-load-more-button', {[`talk-load-more-button-${loadingState}`]: loadingState})}
|
||||
className={cn('talk-load-more-button', {
|
||||
[`talk-load-more-button-${loadingState}`]: loadingState,
|
||||
})}
|
||||
disabled={disabled}
|
||||
>
|
||||
{t('framework.view_more_comments')}
|
||||
|
||||
@@ -1,58 +1,62 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './ModActionButton.css';
|
||||
import {pluginName} from '../../package.json';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { pluginName } from '../../package.json';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
export class ModActionButton extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
on: false
|
||||
on: false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
handleMouseEnter = (e) => {
|
||||
handleMouseEnter = e => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
on: true
|
||||
on: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseLeave = (e) => {
|
||||
handleMouseLeave = e => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
on: false
|
||||
on: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleDeleteTag = () => {
|
||||
this.props.deleteTag();
|
||||
this.props.closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
handlePostTag = () => {
|
||||
this.props.postTag();
|
||||
this.props.closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {alreadyTagged} = this.props;
|
||||
const {handleDeleteTag, handlePostTag} = this;
|
||||
const { alreadyTagged } = this.props;
|
||||
const { handleDeleteTag, handlePostTag } = this;
|
||||
|
||||
return (
|
||||
<button className={cn(`${pluginName}-tag-button`, styles.button, {[styles.featured] : alreadyTagged})}
|
||||
<button
|
||||
className={cn(`${pluginName}-tag-button`, styles.button, {
|
||||
[styles.featured]: alreadyTagged,
|
||||
})}
|
||||
onClick={alreadyTagged ? handleDeleteTag : handlePostTag}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave} >
|
||||
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
{alreadyTagged ? (
|
||||
<span className={styles.approved}>
|
||||
<Icon name="star" className={styles.icon} />
|
||||
{!this.state.on ? t('talk-plugin-featured-comments.featured') : t('talk-plugin-featured-comments.un_feature')}
|
||||
{!this.state.on
|
||||
? t('talk-plugin-featured-comments.featured')
|
||||
: t('talk-plugin-featured-comments.un_feature')}
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
@@ -60,11 +64,9 @@ export class ModActionButton extends React.Component {
|
||||
{t('talk-plugin-featured-comments.feature')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModActionButton;
|
||||
|
||||
|
||||
@@ -2,53 +2,60 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './ModTag.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
export default class ModTag extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
on: false
|
||||
on: false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
handleMouseEnter = (e) => {
|
||||
handleMouseEnter = e => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
on: true
|
||||
on: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleMouseLeave = (e) => {
|
||||
handleMouseLeave = e => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
on: false
|
||||
on: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
openFeaturedDialog = (comment, asset) => {
|
||||
this.props.openFeaturedDialog(comment, asset);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {alreadyTagged, deleteTag, comment, asset} = this.props;
|
||||
const { alreadyTagged, deleteTag, comment, asset } = this.props;
|
||||
|
||||
return alreadyTagged ? (
|
||||
<span className={cn(styles.tag, styles.featured)}
|
||||
<span
|
||||
className={cn(styles.tag, styles.featured)}
|
||||
onClick={deleteTag}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave} >
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
<Icon name="star_outline" className={cn(styles.tagIcon)} />
|
||||
{!this.state.on ? t('talk-plugin-featured-comments.featured') : t('talk-plugin-featured-comments.un_feature')}
|
||||
{!this.state.on
|
||||
? t('talk-plugin-featured-comments.featured')
|
||||
: t('talk-plugin-featured-comments.un_feature')}
|
||||
</span>
|
||||
) : (
|
||||
<span className={cn(styles.tag, {[styles.featured]: alreadyTagged})}
|
||||
onClick={() => this.openFeaturedDialog(comment, asset)} >
|
||||
<span
|
||||
className={cn(styles.tag, { [styles.featured]: alreadyTagged })}
|
||||
onClick={() => this.openFeaturedDialog(comment, asset)}
|
||||
>
|
||||
<Icon name="star_outline" className={cn(styles.tagIcon)} />
|
||||
{alreadyTagged ? t('talk-plugin-featured-comments.featured') : t('talk-plugin-featured-comments.feature')}
|
||||
{alreadyTagged
|
||||
? t('talk-plugin-featured-comments.featured')
|
||||
: t('talk-plugin-featured-comments.feature')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import React from 'react';
|
||||
import {TabCount} from 'plugin-api/beta/client/components/ui';
|
||||
import { TabCount } from 'plugin-api/beta/client/components/ui';
|
||||
import InfoIcon from './InfoIcon';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import Tooltip from './Tooltip';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Tab = ({active, hover, featuredCommentsCount}) => {
|
||||
const Tab = ({ active, hover, featuredCommentsCount }) => {
|
||||
return (
|
||||
<span>
|
||||
{t('talk-plugin-featured-comments.featured')}
|
||||
<TabCount active={active} sub>{featuredCommentsCount}</TabCount>
|
||||
<TabCount active={active} sub>
|
||||
{featuredCommentsCount}
|
||||
</TabCount>
|
||||
<InfoIcon hover={hover} />
|
||||
{hover && <Tooltip />}
|
||||
</span>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import Comment from '../containers/Comment';
|
||||
import LoadMore from './LoadMore';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
import { getErrorMessages } from 'plugin-api/beta/client/utils';
|
||||
|
||||
class TabPane extends React.Component {
|
||||
state = {
|
||||
@@ -9,36 +9,43 @@ class TabPane extends React.Component {
|
||||
};
|
||||
|
||||
loadMore = () => {
|
||||
this.setState({loadingState: 'loading'});
|
||||
this.props.loadMore()
|
||||
this.setState({ loadingState: 'loading' });
|
||||
this.props
|
||||
.loadMore()
|
||||
.then(() => {
|
||||
this.setState({loadingState: 'success'});
|
||||
this.setState({ loadingState: 'success' });
|
||||
})
|
||||
.catch((error) => {
|
||||
this.setState({loadingState: 'error'});
|
||||
.catch(error => {
|
||||
this.setState({ loadingState: 'error' });
|
||||
this.props.notify('error', getErrorMessages(error));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {root, data, asset: {featuredComments, ...asset}, viewComment} = this.props;
|
||||
const {
|
||||
root,
|
||||
data,
|
||||
asset: { featuredComments, ...asset },
|
||||
viewComment,
|
||||
} = this.props;
|
||||
return (
|
||||
<div>
|
||||
{featuredComments.nodes.map((comment) =>
|
||||
{featuredComments.nodes.map(comment => (
|
||||
<Comment
|
||||
key={comment.id}
|
||||
root={root}
|
||||
data={data}
|
||||
comment={comment}
|
||||
asset={asset}
|
||||
viewComment={viewComment} />
|
||||
)}
|
||||
{featuredComments.hasNextPage &&
|
||||
viewComment={viewComment}
|
||||
/>
|
||||
))}
|
||||
{featuredComments.hasNextPage && (
|
||||
<LoadMore
|
||||
loadMore={this.loadMore}
|
||||
loadingState={this.state.loadingState}
|
||||
/>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,39 +2,49 @@ import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './Tag.css';
|
||||
import Tooltip from './Tooltip';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
export default class Tag extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.state = {
|
||||
tooltip: false
|
||||
tooltip: false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
showTooltip = (e) => {
|
||||
showTooltip = e => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
tooltip: true
|
||||
tooltip: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
hideTooltip = (e) => {
|
||||
hideTooltip = e => {
|
||||
e.preventDefault();
|
||||
this.setState({
|
||||
tooltip: false
|
||||
tooltip: false,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {tooltip} = this.state;
|
||||
return(
|
||||
<span className={cn(styles.tagContainer, styles.noSelect)} onMouseEnter={this.showTooltip}
|
||||
onMouseLeave={this.hideTooltip} onTouchStart={this.showTooltip}
|
||||
onTouchEnd={this.hideTooltip} >
|
||||
<span className={cn(styles.tag, styles.noSelect, {[styles.on]: tooltip}, 'talk-stream-comment-featured-tag-label')}>
|
||||
const { tooltip } = this.state;
|
||||
return (
|
||||
<span
|
||||
className={cn(styles.tagContainer, styles.noSelect)}
|
||||
onMouseEnter={this.showTooltip}
|
||||
onMouseLeave={this.hideTooltip}
|
||||
onTouchStart={this.showTooltip}
|
||||
onTouchEnd={this.hideTooltip}
|
||||
>
|
||||
<span
|
||||
className={cn(
|
||||
styles.tag,
|
||||
styles.noSelect,
|
||||
{ [styles.on]: tooltip },
|
||||
'talk-stream-comment-featured-tag-label'
|
||||
)}
|
||||
>
|
||||
{t('talk-plugin-featured-comments.featured')}
|
||||
</span>
|
||||
{tooltip && <Tooltip className={styles.tooltip} />}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './Tooltip.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
export default ({className = ''}) => (
|
||||
export default ({ className = '' }) => (
|
||||
<div className={cn(styles.tooltip, className)}>
|
||||
<Icon name="info_outline" className={styles.icon} />
|
||||
<h3 className={styles.headline}>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import { gql } from 'react-apollo';
|
||||
import Comment from '../components/Comment';
|
||||
import {withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import {getSlotFragmentSpreads} from 'plugin-api/beta/client/utils';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils';
|
||||
|
||||
const slots = [
|
||||
'commentReactions',
|
||||
'commentAuthorName',
|
||||
'commentTimestamp',
|
||||
];
|
||||
const slots = ['commentReactions', 'commentAuthorName', 'commentTimestamp'];
|
||||
|
||||
export default withFragments({
|
||||
root: gql`
|
||||
@@ -39,5 +35,5 @@ export default withFragments({
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
}
|
||||
`
|
||||
`,
|
||||
})(Comment);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import FeaturedButton from '../components/FeaturedButton';
|
||||
import {withTags} from 'plugin-api/beta/client/hocs';
|
||||
import { withTags } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
export default withTags('featured')(FeaturedButton);
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import { compose } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import FeaturedDialog from '../components/FeaturedDialog';
|
||||
import {withTags, connect} from 'plugin-api/beta/client/hocs';
|
||||
import {closeFeaturedDialog} from '../actions';
|
||||
import { withTags, connect } from 'plugin-api/beta/client/hocs';
|
||||
import { closeFeaturedDialog } from '../actions';
|
||||
|
||||
const mapStateToProps = ({talkPluginFeaturedComments: state}) => ({
|
||||
const mapStateToProps = ({ talkPluginFeaturedComments: state }) => ({
|
||||
showFeaturedDialog: state.showFeaturedDialog,
|
||||
comment: state.comment,
|
||||
asset: state.asset,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
closeFeaturedDialog,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
closeFeaturedDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withTags('featured'),
|
||||
withTags('featured')
|
||||
);
|
||||
|
||||
export default enhance(FeaturedDialog);
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import { compose } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import ModActionButton from '../components/ModActionButton';
|
||||
import {withTags, connect} from 'plugin-api/beta/client/hocs';
|
||||
import {closeMenu} from 'plugins/talk-plugin-moderation-actions/client/actions';
|
||||
import { withTags, connect } from 'plugin-api/beta/client/hocs';
|
||||
import { closeMenu } from 'plugins/talk-plugin-moderation-actions/client/actions';
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
closeMenu,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
closeMenu,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
withTags('featured'),
|
||||
connect(null, mapDispatchToProps),
|
||||
connect(null, mapDispatchToProps)
|
||||
);
|
||||
|
||||
export default enhance(ModActionButton);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import {gql} from 'react-apollo';
|
||||
import {connect} from 'react-redux';
|
||||
import { gql } from 'react-apollo';
|
||||
import { connect } from 'react-redux';
|
||||
import Comment from 'coral-admin/src/routes/Moderation/containers/Comment';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import { getDefinitionName } from 'coral-framework/utils';
|
||||
import truncate from 'lodash/truncate';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
function prepareNotificationText(text) {
|
||||
return truncate(text, {length: 50}).replace('\n', ' ');
|
||||
return truncate(text, { length: 50 }).replace('\n', ' ');
|
||||
}
|
||||
|
||||
class ModSubscription extends React.Component {
|
||||
@@ -20,14 +20,18 @@ class ModSubscription extends React.Component {
|
||||
variables: {
|
||||
assetId: this.props.data.variables.asset_id,
|
||||
},
|
||||
updateQuery: (prev, {subscriptionData: {data: {commentFeatured: {user, comment}}}}) => {
|
||||
const notifyText = this.props.user.id === user.id
|
||||
? ''
|
||||
: t(
|
||||
'talk-plugin-featured-comments.notify_featured',
|
||||
user.username,
|
||||
prepareNotificationText(comment.body),
|
||||
);
|
||||
updateQuery: (
|
||||
prev,
|
||||
{ subscriptionData: { data: { commentFeatured: { user, comment } } } }
|
||||
) => {
|
||||
const notifyText =
|
||||
this.props.user.id === user.id
|
||||
? ''
|
||||
: t(
|
||||
'talk-plugin-featured-comments.notify_featured',
|
||||
user.username,
|
||||
prepareNotificationText(comment.body)
|
||||
);
|
||||
return this.props.handleCommentChange(prev, comment, notifyText);
|
||||
},
|
||||
},
|
||||
@@ -36,23 +40,33 @@ class ModSubscription extends React.Component {
|
||||
variables: {
|
||||
assetId: this.props.data.variables.asset_id,
|
||||
},
|
||||
updateQuery: (prev, {subscriptionData: {data: {commentUnfeatured: {user, comment}}}}) => {
|
||||
const notify = this.props.user.id === user.id
|
||||
? ''
|
||||
: t(
|
||||
'talk-plugin-featured-comments.notify_unfeatured',
|
||||
user.username,
|
||||
prepareNotificationText(comment.body),
|
||||
);
|
||||
updateQuery: (
|
||||
prev,
|
||||
{
|
||||
subscriptionData: {
|
||||
data: { commentUnfeatured: { user, comment } },
|
||||
},
|
||||
}
|
||||
) => {
|
||||
const notify =
|
||||
this.props.user.id === user.id
|
||||
? ''
|
||||
: t(
|
||||
'talk-plugin-featured-comments.notify_unfeatured',
|
||||
user.username,
|
||||
prepareNotificationText(comment.body)
|
||||
);
|
||||
return this.props.handleCommentChange(prev, comment, notify);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
this.subscriptions = configs.map((config) => this.props.data.subscribeToMore(config));
|
||||
this.subscriptions = configs.map(config =>
|
||||
this.props.data.subscribeToMore(config)
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
this.subscriptions.forEach(unsubscribe => unsubscribe());
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -98,7 +112,7 @@ const COMMENT_UNFEATURED_SUBSCRIPTION = gql`
|
||||
${Comment.fragments.comment}
|
||||
`;
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
const mapStateToProps = state => ({
|
||||
user: state.auth.user,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import ModTag from '../components/ModTag';
|
||||
import {withTags, connect} from 'plugin-api/beta/client/hocs';
|
||||
import {gql, compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {openFeaturedDialog} from '../actions';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import { withTags, connect } from 'plugin-api/beta/client/hocs';
|
||||
import { gql, compose } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { openFeaturedDialog } from '../actions';
|
||||
import { notify } from 'plugin-api/beta/client/actions/notification';
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
notify,
|
||||
openFeaturedDialog,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
notify,
|
||||
openFeaturedDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const fragments = {
|
||||
comment: gql`
|
||||
@@ -18,11 +21,11 @@ const fragments = {
|
||||
username
|
||||
}
|
||||
}
|
||||
`
|
||||
`,
|
||||
};
|
||||
const enhance = compose(
|
||||
withTags('featured', {fragments}),
|
||||
connect(null, mapDispatchToProps),
|
||||
withTags('featured', { fragments }),
|
||||
connect(null, mapDispatchToProps)
|
||||
);
|
||||
|
||||
export default enhance(ModTag);
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
import Tab from '../components/Tab';
|
||||
import {withFragments, excludeIf} from 'plugin-api/beta/client/hocs';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {withProps} from 'recompose';
|
||||
import { withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import { withProps } from 'recompose';
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
asset: gql`
|
||||
fragment TalkFeaturedComments_Tab_asset on Asset {
|
||||
featuredCommentsCount: totalCommentCount(tags: ["FEATURED"]) @skip(if: $hasComment)
|
||||
}`,
|
||||
featuredCommentsCount: totalCommentCount(tags: ["FEATURED"])
|
||||
@skip(if: $hasComment)
|
||||
}
|
||||
`,
|
||||
}),
|
||||
excludeIf((props) => props.asset.featuredCommentsCount === 0),
|
||||
withProps((props) => ({featuredCommentsCount: props.asset.featuredCommentsCount})),
|
||||
excludeIf(props => props.asset.featuredCommentsCount === 0),
|
||||
withProps(props => ({
|
||||
featuredCommentsCount: props.asset.featuredCommentsCount,
|
||||
}))
|
||||
);
|
||||
|
||||
export default enhance(Tab);
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from 'react';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import TabPane from '../components/TabPane';
|
||||
import {withFragments, connect} from 'plugin-api/beta/client/hocs';
|
||||
import { withFragments, connect } from 'plugin-api/beta/client/hocs';
|
||||
import Comment from '../containers/Comment';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import {viewComment} from 'coral-embed-stream/src/actions/stream';
|
||||
import {appendNewNodes, getDefinitionName} from 'plugin-api/beta/client/utils';
|
||||
import { notify } from 'plugin-api/beta/client/actions/notification';
|
||||
import { viewComment } from 'coral-embed-stream/src/actions/stream';
|
||||
import {
|
||||
appendNewNodes,
|
||||
getDefinitionName,
|
||||
} from 'plugin-api/beta/client/utils';
|
||||
import update from 'immutability-helper';
|
||||
|
||||
class TabPaneContainer extends React.Component {
|
||||
|
||||
loadMore = () => {
|
||||
return this.props.data.fetchMore({
|
||||
query: LOAD_MORE_QUERY,
|
||||
@@ -22,17 +24,17 @@ class TabPaneContainer extends React.Component {
|
||||
sortBy: this.props.data.variables.sortBy,
|
||||
excludeIgnored: this.props.data.variables.excludeIgnored,
|
||||
},
|
||||
updateQuery: (previous, {fetchMoreResult:{comments}}) => {
|
||||
updateQuery: (previous, { fetchMoreResult: { comments } }) => {
|
||||
const updated = update(previous, {
|
||||
asset: {
|
||||
featuredComments: {
|
||||
nodes: {
|
||||
$apply: (nodes) => appendNewNodes(nodes, comments.nodes),
|
||||
$apply: nodes => appendNewNodes(nodes, comments.nodes),
|
||||
},
|
||||
hasNextPage: {$set: comments.hasNextPage},
|
||||
endCursor: {$set: comments.endCursor},
|
||||
hasNextPage: { $set: comments.hasNextPage },
|
||||
endCursor: { $set: comments.endCursor },
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
},
|
||||
@@ -40,10 +42,7 @@ class TabPaneContainer extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
return <TabPane
|
||||
{...this.props}
|
||||
loadMore={this.loadMore}
|
||||
/>;
|
||||
return <TabPane {...this.props} loadMore={this.loadMore} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,11 +77,14 @@ const LOAD_MORE_QUERY = gql`
|
||||
${Comment.fragments.comment}
|
||||
`;
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
viewComment,
|
||||
notify,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
viewComment,
|
||||
notify,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
@@ -118,7 +120,7 @@ const enhance = compose(
|
||||
${Comment.fragments.comment}
|
||||
${Comment.fragments.asset}
|
||||
`,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
export default enhance(TabPaneContainer);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import Tag from '../components/Tag';
|
||||
import {isTagged} from 'plugin-api/beta/client/utils';
|
||||
import {withFragments, excludeIf} from 'plugin-api/beta/client/hocs';
|
||||
import { isTagged } from 'plugin-api/beta/client/utils';
|
||||
import { withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
export default compose(
|
||||
withFragments({
|
||||
@@ -13,6 +13,7 @@ export default compose(
|
||||
}
|
||||
}
|
||||
}
|
||||
`}),
|
||||
excludeIf((props) => !isTagged(props.comment.tags, 'FEATURED'))
|
||||
`,
|
||||
}),
|
||||
excludeIf(props => !isTagged(props.comment.tags, 'FEATURED'))
|
||||
)(Tag);
|
||||
|
||||
@@ -7,11 +7,11 @@ import ModTag from './containers/ModTag';
|
||||
import ModActionButton from './containers/ModActionButton';
|
||||
import ModSubscription from './containers/ModSubscription';
|
||||
import FeaturedDialog from './containers/FeaturedDialog';
|
||||
import {gql} from 'react-apollo';
|
||||
import { gql } from 'react-apollo';
|
||||
import reducer from './reducer';
|
||||
|
||||
import {findCommentInEmbedQuery} from 'coral-embed-stream/src/graphql/utils';
|
||||
import {prependNewNodes} from 'plugin-api/beta/client/utils';
|
||||
import { findCommentInEmbedQuery } from 'coral-embed-stream/src/graphql/utils';
|
||||
import { prependNewNodes } from 'plugin-api/beta/client/utils';
|
||||
|
||||
export default {
|
||||
translations,
|
||||
@@ -25,32 +25,35 @@ export default {
|
||||
adminCommentInfoBar: [ModTag],
|
||||
},
|
||||
mutations: {
|
||||
IgnoreUser: ({variables}) => ({
|
||||
IgnoreUser: ({ variables }) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previous) => {
|
||||
CoralEmbedStream_Embed: previous => {
|
||||
if (!previous.asset.featuredComments) {
|
||||
return previous;
|
||||
}
|
||||
const ignoredUserId = variables.id;
|
||||
const newNodes = previous.asset.featuredComments.nodes.filter((n) => n.user.id !== ignoredUserId);
|
||||
const removedCount = previous.asset.featuredComments.nodes.length - newNodes.length;
|
||||
const newNodes = previous.asset.featuredComments.nodes.filter(
|
||||
n => n.user.id !== ignoredUserId
|
||||
);
|
||||
const removedCount =
|
||||
previous.asset.featuredComments.nodes.length - newNodes.length;
|
||||
const updated = update(previous, {
|
||||
asset: {
|
||||
featuredComments: {
|
||||
nodes: {$set: newNodes}
|
||||
nodes: { $set: newNodes },
|
||||
},
|
||||
featuredCommentsCount: {
|
||||
$apply: (value) => value - removedCount,
|
||||
}
|
||||
}
|
||||
$apply: value => value - removedCount,
|
||||
},
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
AddTag: ({variables}) => ({
|
||||
AddTag: ({ variables }) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previous) => {
|
||||
CoralEmbedStream_Embed: previous => {
|
||||
let updated = previous;
|
||||
|
||||
if (variables.name !== 'FEATURED') {
|
||||
@@ -64,21 +67,20 @@ export default {
|
||||
asset: {
|
||||
featuredComments: {
|
||||
nodes: {
|
||||
$apply: (nodes) => prependNewNodes(nodes, [comment]),
|
||||
}
|
||||
$apply: nodes => prependNewNodes(nodes, [comment]),
|
||||
},
|
||||
},
|
||||
featuredCommentsCount: {
|
||||
$apply: (value) => value + 1
|
||||
$apply: value => value + 1,
|
||||
},
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
},
|
||||
},
|
||||
update: (proxy) => {
|
||||
|
||||
update: proxy => {
|
||||
if (variables.name !== 'FEATURED') {
|
||||
return;
|
||||
}
|
||||
@@ -91,16 +93,16 @@ export default {
|
||||
}
|
||||
`;
|
||||
|
||||
const data = proxy.readFragment({fragment, id: fragmentId});
|
||||
const data = proxy.readFragment({ fragment, id: fragmentId });
|
||||
|
||||
data.status = 'ACCEPTED';
|
||||
|
||||
proxy.writeFragment({fragment, id: fragmentId, data});
|
||||
proxy.writeFragment({ fragment, id: fragmentId, data });
|
||||
},
|
||||
}),
|
||||
RemoveTag: ({variables}) => ({
|
||||
RemoveTag: ({ variables }) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previous) => {
|
||||
CoralEmbedStream_Embed: previous => {
|
||||
let updated = previous;
|
||||
|
||||
if (variables.name !== 'FEATURED') {
|
||||
@@ -112,20 +114,19 @@ export default {
|
||||
asset: {
|
||||
featuredComments: {
|
||||
nodes: {
|
||||
$apply: (nodes) =>
|
||||
nodes.filter((n) => n.id !== variables.id)
|
||||
}
|
||||
$apply: nodes => nodes.filter(n => n.id !== variables.id),
|
||||
},
|
||||
},
|
||||
featuredCommentsCount: {
|
||||
$apply: (value) => value - 1
|
||||
}
|
||||
}
|
||||
$apply: value => value - 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return updated;
|
||||
},
|
||||
}
|
||||
})
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {OPEN_FEATURED_DIALOG, CLOSE_FEATURED_DIALOG} from './constants';
|
||||
import { OPEN_FEATURED_DIALOG, CLOSE_FEATURED_DIALOG } from './constants';
|
||||
|
||||
const initialState = {
|
||||
showFeaturedDialog: false,
|
||||
comment: {
|
||||
id: null,
|
||||
tags: []
|
||||
tags: [],
|
||||
},
|
||||
asset: {
|
||||
id: null,
|
||||
@@ -13,20 +13,20 @@ const initialState = {
|
||||
|
||||
export default function reducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case OPEN_FEATURED_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
comment: action.comment,
|
||||
asset: action.asset,
|
||||
showFeaturedDialog: true,
|
||||
};
|
||||
case CLOSE_FEATURED_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
featuredCommentId: null,
|
||||
showFeaturedDialog: false,
|
||||
};
|
||||
default :
|
||||
return state;
|
||||
case OPEN_FEATURED_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
comment: action.comment,
|
||||
asset: action.asset,
|
||||
showFeaturedDialog: true,
|
||||
};
|
||||
case CLOSE_FEATURED_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
featuredCommentId: null,
|
||||
showFeaturedDialog: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const {check} = require('perms/utils');
|
||||
const { check } = require('perms/utils');
|
||||
|
||||
module.exports = {
|
||||
typeDefs: `
|
||||
@@ -24,18 +24,18 @@ module.exports = {
|
||||
`,
|
||||
resolvers: {
|
||||
Subscription: {
|
||||
commentFeatured: ({user, comment}) => {
|
||||
return {user, comment};
|
||||
commentFeatured: ({ user, comment }) => {
|
||||
return { user, comment };
|
||||
},
|
||||
commentUnfeatured: ({user, comment}) => {
|
||||
return {user, comment};
|
||||
commentUnfeatured: ({ user, comment }) => {
|
||||
return { user, comment };
|
||||
},
|
||||
},
|
||||
},
|
||||
setupFunctions: {
|
||||
commentFeatured: (options, args) => ({
|
||||
commentFeatured: {
|
||||
filter: ({comment}, {user}) => {
|
||||
filter: ({ comment }, { user }) => {
|
||||
if (args.asset_id === null) {
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ module.exports = {
|
||||
}),
|
||||
commentUnfeatured: (options, args) => ({
|
||||
commentUnfeatured: {
|
||||
filter: ({comment}, {user}) => {
|
||||
filter: ({ comment }, { user }) => {
|
||||
if (args.asset_id === null) {
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
}
|
||||
@@ -57,21 +57,32 @@ module.exports = {
|
||||
hooks: {
|
||||
RootMutation: {
|
||||
addTag: {
|
||||
async post(obj, {tag: {name, id, item_type}}, {user, mutators: {Comment}, pubsub}) {
|
||||
async post(
|
||||
obj,
|
||||
{ tag: { name, id, item_type } },
|
||||
{ user, mutators: { Comment }, pubsub }
|
||||
) {
|
||||
if (name === 'FEATURED' && item_type === 'COMMENTS') {
|
||||
const comment = await Comment.setStatus({id: id, status: 'ACCEPTED'});
|
||||
const comment = await Comment.setStatus({
|
||||
id: id,
|
||||
status: 'ACCEPTED',
|
||||
});
|
||||
if (comment) {
|
||||
pubsub.publish('commentFeatured', {comment, user});
|
||||
pubsub.publish('commentFeatured', { comment, user });
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
removeTag: {
|
||||
async post(obj, {tag: {name, id, item_type}}, {user, loaders: {Comments}, pubsub}) {
|
||||
async post(
|
||||
obj,
|
||||
{ tag: { name, id, item_type } },
|
||||
{ user, loaders: { Comments }, pubsub }
|
||||
) {
|
||||
if (name === 'FEATURED' && item_type === 'COMMENTS') {
|
||||
const comment = await Comments.get.load(id);
|
||||
if (comment) {
|
||||
pubsub.publish('commentUnfeatured', {comment, user});
|
||||
pubsub.publish('commentUnfeatured', { comment, user });
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -84,10 +95,10 @@ module.exports = {
|
||||
permissions: {
|
||||
public: true,
|
||||
self: false,
|
||||
roles: ['ADMIN', 'MODERATOR']
|
||||
roles: ['ADMIN', 'MODERATOR'],
|
||||
},
|
||||
models: ['COMMENTS'],
|
||||
created_at: new Date()
|
||||
}
|
||||
]
|
||||
created_at: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import React, {Component} from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './FlagDetails.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Slot, IfSlotIsNotEmpty, CommentDetail} from 'plugin-api/beta/client/components';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import {
|
||||
Slot,
|
||||
IfSlotIsNotEmpty,
|
||||
CommentDetail,
|
||||
} from 'plugin-api/beta/client/components';
|
||||
|
||||
class FlagDetails extends Component {
|
||||
|
||||
render() {
|
||||
const {comment: {actions}, more, data, root, comment} = this.props;
|
||||
const { comment: { actions }, more, data, root, comment } = this.props;
|
||||
|
||||
const flagActions = actions && actions.filter((a) => a.__typename === 'FlagAction');
|
||||
const flagActions =
|
||||
actions && actions.filter(a => a.__typename === 'FlagAction');
|
||||
const summaries = flagActions.reduce((sum, action) => {
|
||||
if (!(action.reason in sum)) {
|
||||
sum[action.reason] = {count: 0, actions: []};
|
||||
sum[action.reason] = { count: 0, actions: [] };
|
||||
}
|
||||
sum[action.reason].count++;
|
||||
if (action.user) {
|
||||
@@ -30,16 +34,21 @@ class FlagDetails extends Component {
|
||||
return (
|
||||
<CommentDetail
|
||||
icon={'flag'}
|
||||
header={`${t('talk-plugin-flag-details.flags')} (${Object.keys(summaries).length})`}
|
||||
header={`${t('talk-plugin-flag-details.flags')} (${
|
||||
Object.keys(summaries).length
|
||||
})`}
|
||||
info={
|
||||
<ul className={styles.info}>
|
||||
{reasons.map((reason) =>
|
||||
{reasons.map(reason => (
|
||||
<li key={reason} className={styles.lessDetail}>
|
||||
{t(`flags.reasons.comment.${reason.toLowerCase()}`)} {summaries[reason].userFlagged && `(${summaries[reason].count})`}
|
||||
{t(`flags.reasons.comment.${reason.toLowerCase()}`)}{' '}
|
||||
{summaries[reason].userFlagged &&
|
||||
`(${summaries[reason].count})`}
|
||||
</li>
|
||||
)}
|
||||
))}
|
||||
</ul>
|
||||
}>
|
||||
}
|
||||
>
|
||||
{more && (
|
||||
<IfSlotIsNotEmpty
|
||||
slot="adminCommentMoreFlagDetails"
|
||||
@@ -62,10 +71,12 @@ FlagDetails.propTypes = {
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
comment: PropTypes.shape({
|
||||
actions: PropTypes.arrayOf(PropTypes.shape({
|
||||
message: PropTypes.string,
|
||||
user: PropTypes.shape({username: PropTypes.string})
|
||||
})).isRequired,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
message: PropTypes.string,
|
||||
user: PropTypes.shape({ username: PropTypes.string }),
|
||||
})
|
||||
).isRequired,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React, {Component} from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import styles from './UserFlagDetails.css';
|
||||
|
||||
class UserFlagDetails extends Component {
|
||||
|
||||
render() {
|
||||
const {comment: {actions}, viewUserDetail} = this.props;
|
||||
const { comment: { actions }, viewUserDetail } = this.props;
|
||||
|
||||
const flagActions = actions && actions.filter((a) => a.__typename === 'FlagAction');
|
||||
const flagActions =
|
||||
actions && actions.filter(a => a.__typename === 'FlagAction');
|
||||
const summaries = flagActions.reduce((sum, action) => {
|
||||
if (!action.user) {
|
||||
return sum;
|
||||
}
|
||||
if (!(action.reason in sum)) {
|
||||
sum[action.reason] = {count: 0, actions: []};
|
||||
sum[action.reason] = { count: 0, actions: [] };
|
||||
}
|
||||
sum[action.reason].count++;
|
||||
sum[action.reason].actions.push(action);
|
||||
@@ -23,25 +23,28 @@ class UserFlagDetails extends Component {
|
||||
|
||||
return (
|
||||
<ul className={styles.detail}>
|
||||
{Object.keys(summaries)
|
||||
.map((reason) => (
|
||||
<li key={reason}>
|
||||
{t(`flags.reasons.comment.${reason.toLowerCase()}`)} ({summaries[reason].count})
|
||||
<ul className={styles.subDetail}>
|
||||
{summaries[reason].actions.map((action) =>
|
||||
<li key={action.user.id}>
|
||||
{action.user &&
|
||||
<a className={styles.username} onClick={() => viewUserDetail(action.user.id)}>
|
||||
{action.user.username}
|
||||
</a>
|
||||
}
|
||||
{action.message}
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</li>
|
||||
))
|
||||
}
|
||||
{Object.keys(summaries).map(reason => (
|
||||
<li key={reason}>
|
||||
{t(`flags.reasons.comment.${reason.toLowerCase()}`)} ({
|
||||
summaries[reason].count
|
||||
})
|
||||
<ul className={styles.subDetail}>
|
||||
{summaries[reason].actions.map(action => (
|
||||
<li key={action.user.id}>
|
||||
{action.user && (
|
||||
<a
|
||||
className={styles.username}
|
||||
onClick={() => viewUserDetail(action.user.id)}
|
||||
>
|
||||
{action.user.username}
|
||||
</a>
|
||||
)}
|
||||
{action.message}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
@@ -49,10 +52,12 @@ class UserFlagDetails extends Component {
|
||||
|
||||
UserFlagDetails.propTypes = {
|
||||
comment: PropTypes.shape({
|
||||
actions: PropTypes.arrayOf(PropTypes.shape({
|
||||
message: PropTypes.string,
|
||||
user: PropTypes.shape({username: PropTypes.string})
|
||||
})).isRequired,
|
||||
actions: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
message: PropTypes.string,
|
||||
user: PropTypes.shape({ username: PropTypes.string }),
|
||||
})
|
||||
).isRequired,
|
||||
}).isRequired,
|
||||
viewUserDetail: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import FlagDetails from '../components/FlagDetails';
|
||||
import {withFragments, excludeIf} from 'plugin-api/beta/client/hocs';
|
||||
import {getSlotFragmentSpreads} from 'plugin-api/beta/client/utils';
|
||||
import { withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
|
||||
import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils';
|
||||
|
||||
const slots = [
|
||||
'adminCommentMoreFlagDetails',
|
||||
];
|
||||
const slots = ['adminCommentMoreFlagDetails'];
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
@@ -31,9 +29,12 @@ const enhance = compose(
|
||||
}
|
||||
${getSlotFragmentSpreads(slots, 'comment')}
|
||||
}
|
||||
`
|
||||
`,
|
||||
}),
|
||||
excludeIf(({comment: {actions}}) => !actions.some((action) => action.__typename === 'FlagAction')),
|
||||
excludeIf(
|
||||
({ comment: { actions } }) =>
|
||||
!actions.some(action => action.__typename === 'FlagAction')
|
||||
)
|
||||
);
|
||||
|
||||
export default enhance(FlagDetails);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import UserFlagDetails from '../components/UserFlagDetails';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {withFragments, excludeIf} from 'plugin-api/beta/client/hocs';
|
||||
import {viewUserDetail} from 'plugin-api/beta/client/actions/admin';
|
||||
import {connect} from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
|
||||
import { viewUserDetail } from 'plugin-api/beta/client/actions/admin';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
...bindActionCreators({
|
||||
viewUserDetail,
|
||||
}, dispatch)
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
...bindActionCreators(
|
||||
{
|
||||
viewUserDetail,
|
||||
},
|
||||
dispatch
|
||||
),
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
@@ -29,11 +32,12 @@ const enhance = compose(
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`,
|
||||
}),
|
||||
excludeIf(({comment: {actions}}) =>
|
||||
!actions.some((action) => action.__typename === 'FlagAction' && action.user
|
||||
)),
|
||||
excludeIf(
|
||||
({ comment: { actions } }) =>
|
||||
!actions.some(action => action.__typename === 'FlagAction' && action.user)
|
||||
)
|
||||
);
|
||||
|
||||
export default enhance(UserFlagDetails);
|
||||
|
||||
@@ -7,5 +7,5 @@ export default {
|
||||
slots: {
|
||||
adminCommentDetailArea: [FlagDetails],
|
||||
adminCommentMoreFlagDetails: [UserFlagDetails],
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import styles from './IgnoreUserAction.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({ignoreUser}) => (
|
||||
export default ({ ignoreUser }) => (
|
||||
<button
|
||||
className={cn(styles.button, 'talk-plugin-ignore-user-action')}
|
||||
onClick={ignoreUser}>
|
||||
onClick={ignoreUser}
|
||||
>
|
||||
{t('talk-plugin-ignore-user.ignore_user')}
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import styles from './IgnoreUserConfirmation.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default ({ignoreUser, cancel, username}) => (
|
||||
export default ({ ignoreUser, cancel, username }) => (
|
||||
<aside className={cn(styles.root, 'talk-plugin-ignore-user-confirmation')}>
|
||||
<div className={styles.message}>
|
||||
<h1 className={styles.title}>
|
||||
@@ -11,11 +11,28 @@ export default ({ignoreUser, cancel, username}) => (
|
||||
</h1>
|
||||
{t('talk-plugin-ignore-user.confirmation')}
|
||||
</div>
|
||||
<div className={cn(styles.actions, 'talk-plugin-ignore-user-confirmation-actions')}>
|
||||
<button className={cn(styles.cancel, 'talk-plugin-ignore-user-confirmation-cancel')} onClick={cancel}>
|
||||
<div
|
||||
className={cn(
|
||||
styles.actions,
|
||||
'talk-plugin-ignore-user-confirmation-actions'
|
||||
)}
|
||||
>
|
||||
<button
|
||||
className={cn(
|
||||
styles.cancel,
|
||||
'talk-plugin-ignore-user-confirmation-cancel'
|
||||
)}
|
||||
onClick={cancel}
|
||||
>
|
||||
{t('talk-plugin-ignore-user.cancel')}
|
||||
</button>
|
||||
<button className={cn(styles.button, 'talk-plugin-ignore-user-confirmation-button')} onClick={ignoreUser}>
|
||||
<button
|
||||
className={cn(
|
||||
styles.button,
|
||||
'talk-plugin-ignore-user-confirmation-button'
|
||||
)}
|
||||
onClick={ignoreUser}
|
||||
>
|
||||
{t('talk-plugin-ignore-user.ignore_user')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import styles from './IgnoredUserSection.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
export default ({ignoredUsers, stopIgnoringUser}) => (
|
||||
export default ({ ignoredUsers, stopIgnoringUser }) => (
|
||||
<section className={'talk-plugin-ignore-user-section'}>
|
||||
<h3>{t('talk-plugin-ignore-user.section_title')}</h3>
|
||||
<p>{t('talk-plugin-ignore-user.section_info')}</p>
|
||||
<ul className={styles.list}>
|
||||
{ignoredUsers.map(({username, id}) => (
|
||||
{ignoredUsers.map(({ username, id }) => (
|
||||
<li className={styles.listItem} key={id}>
|
||||
<span className={styles.username}>{username}</span>
|
||||
<button
|
||||
onClick={() => stopIgnoringUser({id})}
|
||||
onClick={() => stopIgnoringUser({ id })}
|
||||
className={styles.button}
|
||||
>
|
||||
{t('talk-plugin-ignore-user.stop_ignoring')}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import React from 'react';
|
||||
import IgnoreUserAction from '../components/IgnoreUserAction';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {connect, withFragments, excludeIf} from 'plugin-api/beta/client/hocs';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {setContentSlot} from 'plugins/talk-plugin-author-menu/client/actions';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import { connect, withFragments, excludeIf } from 'plugin-api/beta/client/hocs';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { setContentSlot } from 'plugins/talk-plugin-author-menu/client/actions';
|
||||
import IgnoreUserConfirmation from './IgnoreUserConfirmation';
|
||||
import {getDefinitionName} from 'plugin-api/beta/client/utils';
|
||||
import { getDefinitionName } from 'plugin-api/beta/client/utils';
|
||||
|
||||
class IgnoreUserActionContainer extends React.Component {
|
||||
|
||||
ignoreUser = () => {
|
||||
this.props.setContentSlot('ignoreUserConfirmation');
|
||||
};
|
||||
|
||||
render() {
|
||||
return <IgnoreUserAction
|
||||
ignoreUser={this.ignoreUser}
|
||||
/>;
|
||||
return <IgnoreUserAction ignoreUser={this.ignoreUser} />;
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
setContentSlot,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
setContentSlot,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const withIgnoreUserActionFragments = withFragments({
|
||||
root: gql`
|
||||
@@ -47,7 +47,7 @@ const withIgnoreUserActionFragments = withFragments({
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withIgnoreUserActionFragments,
|
||||
excludeIf(({root: {me}, comment}) => !me || me.id === comment.user.id),
|
||||
excludeIf(({ root: { me }, comment }) => !me || me.id === comment.user.id)
|
||||
);
|
||||
|
||||
export default enhance(IgnoreUserActionContainer);
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import React from 'react';
|
||||
import IgnoreUserConfirmation from '../components/IgnoreUserConfirmation';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {connect, withFragments, withIgnoreUser} from 'plugin-api/beta/client/hocs';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {closeMenu} from 'plugins/talk-plugin-author-menu/client/actions';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import {
|
||||
connect,
|
||||
withFragments,
|
||||
withIgnoreUser,
|
||||
} from 'plugin-api/beta/client/hocs';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { closeMenu } from 'plugins/talk-plugin-author-menu/client/actions';
|
||||
import { notify } from 'plugin-api/beta/client/actions/notification';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { getErrorMessages } from 'plugin-api/beta/client/utils';
|
||||
|
||||
class IgnoreUserConfirmationContainer extends React.Component {
|
||||
|
||||
ignoreUser = () => {
|
||||
const {ignoreUser, notify, comment, closeMenu} = this.props;
|
||||
const { ignoreUser, notify, comment, closeMenu } = this.props;
|
||||
ignoreUser(comment.user.id)
|
||||
.then(() => {
|
||||
notify('success', t('talk-plugin-ignore-user.notify_success', comment.user.username));
|
||||
notify(
|
||||
'success',
|
||||
t('talk-plugin-ignore-user.notify_success', comment.user.username)
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
notify('error', getErrorMessages(err));
|
||||
});
|
||||
closeMenu();
|
||||
@@ -24,22 +30,27 @@ class IgnoreUserConfirmationContainer extends React.Component {
|
||||
|
||||
cancel = () => {
|
||||
this.props.closeMenu();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <IgnoreUserConfirmation
|
||||
username={this.props.comment.user.username}
|
||||
ignoreUser={this.ignoreUser}
|
||||
cancel={this.cancel}
|
||||
/>;
|
||||
return (
|
||||
<IgnoreUserConfirmation
|
||||
username={this.props.comment.user.username}
|
||||
ignoreUser={this.ignoreUser}
|
||||
cancel={this.cancel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
closeMenu,
|
||||
notify,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
closeMenu,
|
||||
notify,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const withIgnoreUserConfirmationFragments = withFragments({
|
||||
comment: gql`
|
||||
@@ -48,13 +59,14 @@ const withIgnoreUserConfirmationFragments = withFragments({
|
||||
id
|
||||
username
|
||||
}
|
||||
}`,
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withIgnoreUserConfirmationFragments,
|
||||
withIgnoreUser,
|
||||
withIgnoreUser
|
||||
);
|
||||
|
||||
export default enhance(IgnoreUserConfirmationContainer);
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import React from 'react';
|
||||
import IgnoredUserSection from '../components/IgnoredUserSection';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {withFragments, excludeIf, withStopIgnoringUser} from 'plugin-api/beta/client/hocs';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import {
|
||||
withFragments,
|
||||
excludeIf,
|
||||
withStopIgnoringUser,
|
||||
} from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class IgnoredUserSectionContainer extends React.Component {
|
||||
|
||||
render() {
|
||||
return <IgnoredUserSection
|
||||
stopIgnoringUser={this.props.stopIgnoringUser}
|
||||
ignoredUsers={this.props.root.me.ignoredUsers}
|
||||
/>;
|
||||
return (
|
||||
<IgnoredUserSection
|
||||
stopIgnoringUser={this.props.stopIgnoringUser}
|
||||
ignoredUsers={this.props.root.me.ignoredUsers}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +24,8 @@ const withIgnoredUserSectionFragments = withFragments({
|
||||
me {
|
||||
id
|
||||
ignoredUsers {
|
||||
id,
|
||||
username,
|
||||
id
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +35,7 @@ const withIgnoredUserSectionFragments = withFragments({
|
||||
const enhance = compose(
|
||||
withIgnoredUserSectionFragments,
|
||||
withStopIgnoringUser,
|
||||
excludeIf(({root: {me}}) => me.ignoredUsers.length === 0),
|
||||
excludeIf(({ root: { me } }) => me.ignoredUsers.length === 0)
|
||||
);
|
||||
|
||||
export default enhance(IgnoredUserSectionContainer);
|
||||
|
||||
@@ -12,32 +12,46 @@ export default {
|
||||
},
|
||||
translations,
|
||||
mutations: {
|
||||
IgnoreUser: ({variables}) => ({
|
||||
IgnoreUser: ({ variables }) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Embed: (previousData) => {
|
||||
CoralEmbedStream_Embed: previousData => {
|
||||
const ignoredUserId = variables.id;
|
||||
const updated = update(previousData, {me: {ignoredUsers: {$push: [{
|
||||
id: ignoredUserId,
|
||||
__typename: 'User',
|
||||
}]}}});
|
||||
const updated = update(previousData, {
|
||||
me: {
|
||||
ignoredUsers: {
|
||||
$push: [
|
||||
{
|
||||
id: ignoredUserId,
|
||||
__typename: 'User',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
StopIgnoringUser: ({variables}) => ({
|
||||
StopIgnoringUser: ({ variables }) => ({
|
||||
updateQueries: {
|
||||
CoralEmbedStream_Profile: (previousData) => {
|
||||
CoralEmbedStream_Profile: previousData => {
|
||||
const noLongerIgnoredUserId = variables.id;
|
||||
|
||||
// remove noLongerIgnoredUserId from ignoredUsers
|
||||
const updated = update(previousData, {me: {ignoredUsers: {
|
||||
$apply: (ignoredUsers) => {
|
||||
return ignoredUsers.filter((u) => u.id !== noLongerIgnoredUserId);
|
||||
}
|
||||
}}});
|
||||
const updated = update(previousData, {
|
||||
me: {
|
||||
ignoredUsers: {
|
||||
$apply: ignoredUsers => {
|
||||
return ignoredUsers.filter(
|
||||
u => u.id !== noLongerIgnoredUserId
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import {withReaction} from 'plugin-api/beta/client/hocs';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { withReaction } from 'plugin-api/beta/client/hocs';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
const plugin = 'talk-plugin-like';
|
||||
@@ -31,15 +31,23 @@ class LikeButton extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {count, alreadyReacted} = this.props;
|
||||
const { count, alreadyReacted } = this.props;
|
||||
return (
|
||||
<div className={cn(styles.container, `${plugin}-container`)}>
|
||||
<button
|
||||
className={cn(styles.button, {[`${styles.liked} talk-plugin-like-liked`]: alreadyReacted}, `${plugin}-button`)}
|
||||
className={cn(
|
||||
styles.button,
|
||||
{ [`${styles.liked} talk-plugin-like-liked`]: alreadyReacted },
|
||||
`${plugin}-button`
|
||||
)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<span className={cn(`${plugin}-label`, styles.label)}>
|
||||
{t(alreadyReacted ? 'talk-plugin-like.liked' : 'talk-plugin-like.like')}
|
||||
{t(
|
||||
alreadyReacted
|
||||
? 'talk-plugin-like.liked'
|
||||
: 'talk-plugin-like.like'
|
||||
)}
|
||||
</span>
|
||||
<Icon name="thumb_up" className={cn(`${plugin}-icon`, styles.icon)} />
|
||||
<span className={`${plugin}-count`}>{count > 0 && count}</span>
|
||||
|
||||
@@ -4,6 +4,6 @@ import translations from './translations.yml';
|
||||
export default {
|
||||
translations,
|
||||
slots: {
|
||||
commentReactions: [LikeButton]
|
||||
commentReactions: [LikeButton],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
const {getReactionConfig} = require('../../plugin-api/beta/server');
|
||||
const { getReactionConfig } = require('../../plugin-api/beta/server');
|
||||
module.exports = getReactionConfig('like');
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import styles from './styles.css';
|
||||
import {withReaction} from 'plugin-api/beta/client/hocs';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { withReaction } from 'plugin-api/beta/client/hocs';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
const plugin = 'talk-plugin-love';
|
||||
@@ -31,15 +31,23 @@ class LoveButton extends React.Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {count, alreadyReacted} = this.props;
|
||||
const { count, alreadyReacted } = this.props;
|
||||
return (
|
||||
<div className={cn(styles.container, `${plugin}-container`)}>
|
||||
<button
|
||||
className={cn(styles.button, {[styles.loved]: alreadyReacted}, `${plugin}-button`)}
|
||||
className={cn(
|
||||
styles.button,
|
||||
{ [styles.loved]: alreadyReacted },
|
||||
`${plugin}-button`
|
||||
)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<span className={cn(`${plugin}-label`, styles.label)}>
|
||||
{t(alreadyReacted ? 'talk-plugin-love.loved' : 'talk-plugin-love.love')}
|
||||
{t(
|
||||
alreadyReacted
|
||||
? 'talk-plugin-love.loved'
|
||||
: 'talk-plugin-love.love'
|
||||
)}
|
||||
</span>
|
||||
<Icon name="favorite" className={cn(`${plugin}-icon`, styles.icon)} />
|
||||
<span className={`${plugin}-count`}>{count > 0 && count}</span>
|
||||
|
||||
@@ -4,6 +4,6 @@ import translations from './translations.yml';
|
||||
export default {
|
||||
translations,
|
||||
slots: {
|
||||
commentReactions: [LoveButton]
|
||||
}
|
||||
commentReactions: [LoveButton],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
const {getReactionConfig} = require('../../plugin-api/beta/server');
|
||||
const { getReactionConfig } = require('../../plugin-api/beta/server');
|
||||
module.exports = getReactionConfig('love');
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import React from 'react';
|
||||
import styles from './MemberSinceInfo.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
import moment from 'moment';
|
||||
|
||||
export default ({memberSinceDate}) => (
|
||||
export default ({ memberSinceDate }) => (
|
||||
<div className={cn(styles.root, 'talk-plugin-member-since')}>
|
||||
<Icon name="date_range" className={cn(styles.icon, 'talk-plugin-member-since-icon')} />
|
||||
<Icon
|
||||
name="date_range"
|
||||
className={cn(styles.icon, 'talk-plugin-member-since-icon')}
|
||||
/>
|
||||
<span className={cn(styles.memberSince, 'talk-plugin-member-since-date')}>
|
||||
{t('talk-plugin-member-since.member_since')}: {moment(new Date(memberSinceDate)).format('MMM DD, YYYY')}
|
||||
{t('talk-plugin-member-since.member_since')}:{' '}
|
||||
{moment(new Date(memberSinceDate)).format('MMM DD, YYYY')}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import MemberSinceInfo from '../components/MemberSinceInfo';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import {withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class MemberSinceInfoContainer extends React.Component {
|
||||
render() {
|
||||
return <MemberSinceInfo
|
||||
memberSinceDate={this.props.comment.user.created_at}
|
||||
/>;
|
||||
return (
|
||||
<MemberSinceInfo memberSinceDate={this.props.comment.user.created_at} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ const withMemberSinceInfoFragments = withFragments({
|
||||
username
|
||||
created_at
|
||||
}
|
||||
}`,
|
||||
}
|
||||
`,
|
||||
});
|
||||
|
||||
const enhance = compose(
|
||||
withMemberSinceInfoFragments,
|
||||
);
|
||||
const enhance = compose(withMemberSinceInfoFragments);
|
||||
|
||||
export default enhance(MemberSinceInfoContainer);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import MemberSinceInfo from './containers/MemberSinceInfo';
|
||||
import translations from './translations.yml';
|
||||
import {gql} from 'react-apollo';
|
||||
import { gql } from 'react-apollo';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
authorMenuInfos: [MemberSinceInfo]
|
||||
authorMenuInfos: [MemberSinceInfo],
|
||||
},
|
||||
translations,
|
||||
fragments: {
|
||||
@@ -15,7 +15,8 @@ export default {
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}`,
|
||||
}
|
||||
`,
|
||||
},
|
||||
mutations: {
|
||||
PostComment: () => ({
|
||||
@@ -24,10 +25,10 @@ export default {
|
||||
comment: {
|
||||
user: {
|
||||
created_at: new Date(),
|
||||
__typename: 'User'
|
||||
__typename: 'User',
|
||||
},
|
||||
__typename: 'Comment'
|
||||
}
|
||||
__typename: 'Comment',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import {OPEN_MENU, CLOSE_MENU, OPEN_BAN_DIALOG, CLOSE_BAN_DIALOG} from './constants';
|
||||
import {
|
||||
OPEN_MENU,
|
||||
CLOSE_MENU,
|
||||
OPEN_BAN_DIALOG,
|
||||
CLOSE_BAN_DIALOG,
|
||||
} from './constants';
|
||||
|
||||
export const openMenu = (id) => ({
|
||||
export const openMenu = id => ({
|
||||
type: OPEN_MENU,
|
||||
id,
|
||||
});
|
||||
@@ -9,11 +14,11 @@ export const closeMenu = () => ({
|
||||
type: CLOSE_MENU,
|
||||
});
|
||||
|
||||
export const openBanDialog = ({commentId, authorId, commentStatus}) => ({
|
||||
export const openBanDialog = ({ commentId, authorId, commentStatus }) => ({
|
||||
type: OPEN_BAN_DIALOG,
|
||||
commentId,
|
||||
authorId,
|
||||
commentStatus
|
||||
commentStatus,
|
||||
});
|
||||
|
||||
export const closeBanDialog = () => ({
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './styles.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
const isApproved = (status) => (status === 'ACCEPTED');
|
||||
const isApproved = status => status === 'ACCEPTED';
|
||||
|
||||
const ApproveCommentAction = ({approveComment, comment: {status}}) => (
|
||||
const ApproveCommentAction = ({ approveComment, comment: { status } }) =>
|
||||
isApproved(status) ? (
|
||||
<span className={styles.approved}>
|
||||
<Icon name="check_circle" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.approved_comment')}
|
||||
</span>
|
||||
) : (
|
||||
<button className={cn(styles.button, 'talk-plugin-moderation-actions-approve')} onClick={approveComment}>
|
||||
<button
|
||||
className={cn(styles.button, 'talk-plugin-moderation-actions-approve')}
|
||||
onClick={approveComment}
|
||||
>
|
||||
<Icon name="done" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.approve_comment')}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
ApproveCommentAction.propTypes = {
|
||||
approveComment: PropTypes.func,
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './styles.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import cn from 'classnames';
|
||||
|
||||
const BanUserAction = ({onBanUser}) => (
|
||||
<button
|
||||
const BanUserAction = ({ onBanUser }) => (
|
||||
<button
|
||||
className={cn(styles.button, 'talk-plugin-moderation-actions-ban')}
|
||||
onClick={onBanUser} >
|
||||
onClick={onBanUser}
|
||||
>
|
||||
<Icon name="block" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.ban_user')}
|
||||
</button>
|
||||
|
||||
@@ -2,12 +2,17 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './BanUserDialog.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Dialog, Button} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Dialog, Button } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
const BanUserDialog = ({showBanDialog, closeBanDialog, banUser}) => (
|
||||
<Dialog open={showBanDialog} className={cn(styles.dialog, 'talk-ban-user-dialog')}>
|
||||
<span className={styles.close} onClick={closeBanDialog}>×</span>
|
||||
const BanUserDialog = ({ showBanDialog, closeBanDialog, banUser }) => (
|
||||
<Dialog
|
||||
open={showBanDialog}
|
||||
className={cn(styles.dialog, 'talk-ban-user-dialog')}
|
||||
>
|
||||
<span className={styles.close} onClick={closeBanDialog}>
|
||||
×
|
||||
</span>
|
||||
<h2>{t('talk-plugin-moderation-actions.ban_user_dialog_headline')}</h2>
|
||||
<h3>{t('talk-plugin-moderation-actions.ban_user_dialog_sub')}</h3>
|
||||
<p className={styles.copy}>
|
||||
@@ -18,14 +23,16 @@ const BanUserDialog = ({showBanDialog, closeBanDialog, banUser}) => (
|
||||
cStyle="cancel"
|
||||
onClick={closeBanDialog}
|
||||
className={cn(styles.cancel, 'talk-ban-user-dialog-button-cancel')}
|
||||
raised >
|
||||
raised
|
||||
>
|
||||
{t('talk-plugin-moderation-actions.ban_user_dialog_cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
cStyle="black"
|
||||
onClick={banUser}
|
||||
className={cn(styles.confirm, 'talk-ban-user-dialog-button-confirm')}
|
||||
raised >
|
||||
raised
|
||||
>
|
||||
{t('talk-plugin-moderation-actions.ban_user_dialog_yes')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,9 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './Menu.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
const Menu = ({className = '', children}) => (
|
||||
const Menu = ({ className = '', children }) => (
|
||||
<div className={cn(styles.menu, className)}>
|
||||
<h3 className={styles.headline}>
|
||||
{t('talk-plugin-moderation-actions.moderation_actions')}
|
||||
|
||||
@@ -3,35 +3,58 @@ import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import Menu from './Menu';
|
||||
import styles from './ModerationActions.css';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
import ClickOutside from 'coral-framework/components/ClickOutside';
|
||||
import RejectCommentAction from '../containers/RejectCommentAction';
|
||||
import ApproveCommentAction from '../containers/ApproveCommentAction';
|
||||
import BanUserAction from '../containers/BanUserAction';
|
||||
import {Slot} from 'plugin-api/beta/client/components';
|
||||
import { Slot } from 'plugin-api/beta/client/components';
|
||||
|
||||
export default class ModerationActions extends React.Component {
|
||||
render() {
|
||||
const {comment, root, asset, data, menuVisible, toogleMenu, hideMenu} = this.props;
|
||||
const {
|
||||
comment,
|
||||
root,
|
||||
asset,
|
||||
data,
|
||||
menuVisible,
|
||||
toogleMenu,
|
||||
hideMenu,
|
||||
} = this.props;
|
||||
|
||||
return(
|
||||
return (
|
||||
<ClickOutside onClickOutside={hideMenu}>
|
||||
<div className={cn(styles.moderationActions, 'talk-plugin-moderation-actions')}>
|
||||
<span onClick={toogleMenu} className={cn(styles.arrow, 'talk-plugin-moderation-actions-arrow')}>
|
||||
{menuVisible ? <Icon name="keyboard_arrow_up" className={styles.icon} /> :
|
||||
<Icon name="keyboard_arrow_down" className={styles.icon} />}
|
||||
<div
|
||||
className={cn(
|
||||
styles.moderationActions,
|
||||
'talk-plugin-moderation-actions'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
onClick={toogleMenu}
|
||||
className={cn(styles.arrow, 'talk-plugin-moderation-actions-arrow')}
|
||||
>
|
||||
{menuVisible ? (
|
||||
<Icon name="keyboard_arrow_up" className={styles.icon} />
|
||||
) : (
|
||||
<Icon name="keyboard_arrow_down" className={styles.icon} />
|
||||
)}
|
||||
</span>
|
||||
{menuVisible && (
|
||||
<Menu className="talk-plugin-modetarion-actions-menu">
|
||||
<Slot
|
||||
className="talk-plugin-modetarion-actions-slot"
|
||||
fill="moderationActions"
|
||||
queryData={{comment, asset}}
|
||||
queryData={{ comment, asset }}
|
||||
data={data}
|
||||
/>
|
||||
<ApproveCommentAction comment={comment} hideMenu={hideMenu} />
|
||||
<RejectCommentAction comment={comment} hideMenu={hideMenu} />
|
||||
<BanUserAction comment={comment} root={root} hideMenu={hideMenu} />
|
||||
<BanUserAction
|
||||
comment={comment}
|
||||
root={root}
|
||||
hideMenu={hideMenu}
|
||||
/>
|
||||
</Menu>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,14 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import styles from './styles.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {Icon} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { Icon } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
const RejectCommentAction = ({rejectComment}) => (
|
||||
<button className={cn(styles.button, 'talk-plugin-moderation-actions-reject')} onClick={rejectComment}>
|
||||
const RejectCommentAction = ({ rejectComment }) => (
|
||||
<button
|
||||
className={cn(styles.button, 'talk-plugin-moderation-actions-reject')}
|
||||
onClick={rejectComment}
|
||||
>
|
||||
<Icon name="clear" className={styles.icon} />
|
||||
{t('talk-plugin-moderation-actions.reject_comment')}
|
||||
</button>
|
||||
|
||||
@@ -1,38 +1,44 @@
|
||||
import React from 'react';
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import { compose } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { getErrorMessages } from 'plugin-api/beta/client/utils';
|
||||
import { notify } from 'plugin-api/beta/client/actions/notification';
|
||||
import ApproveCommentAction from '../components/ApproveCommentAction';
|
||||
import {connect, withSetCommentStatus} from 'plugin-api/beta/client/hocs';
|
||||
import { connect, withSetCommentStatus } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class ApproveCommentActionContainer extends React.Component {
|
||||
|
||||
approveComment = async () => {
|
||||
const {setCommentStatus, comment, hideMenu, notify} = this.props;
|
||||
const { setCommentStatus, comment, hideMenu, notify } = this.props;
|
||||
|
||||
try {
|
||||
await setCommentStatus({
|
||||
commentId: comment.id,
|
||||
status: 'ACCEPTED'
|
||||
status: 'ACCEPTED',
|
||||
});
|
||||
}
|
||||
catch(err) {
|
||||
} catch (err) {
|
||||
notify('error', getErrorMessages(err));
|
||||
}
|
||||
|
||||
hideMenu();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ApproveCommentAction comment={this.props.comment} approveComment={this.approveComment}/>;
|
||||
return (
|
||||
<ApproveCommentAction
|
||||
comment={this.props.comment}
|
||||
approveComment={this.approveComment}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
notify
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
notify,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
import React from 'react';
|
||||
import {compose} from 'react-apollo';
|
||||
import {openBanDialog} from '../actions';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import { compose } from 'react-apollo';
|
||||
import { openBanDialog } from '../actions';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import BanUserAction from '../components/BanUserAction';
|
||||
import {connect} from 'plugin-api/beta/client/hocs';
|
||||
import { connect } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class BanUserActionContainer extends React.Component {
|
||||
onBanUser = () => {
|
||||
this.props.openBanDialog({
|
||||
commentId: this.props.comment.id,
|
||||
commentStatus: this.props.comment.status,
|
||||
authorId: this.props.comment.user.id
|
||||
authorId: this.props.comment.user.id,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {root: {me}, comment} = this.props;
|
||||
return (me.id !== comment.user.id) ?
|
||||
<BanUserAction
|
||||
onBanUser={this.onBanUser}
|
||||
comment={this.props.comment}
|
||||
/> : null;
|
||||
const { root: { me }, comment } = this.props;
|
||||
return me.id !== comment.user.id ? (
|
||||
<BanUserAction onBanUser={this.onBanUser} comment={this.props.comment} />
|
||||
) : null;
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
openBanDialog
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
openBanDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps)
|
||||
);
|
||||
const enhance = compose(connect(null, mapDispatchToProps));
|
||||
|
||||
export default enhance(BanUserActionContainer);
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {closeBanDialog, closeMenu} from '../actions';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import {connect, withSetCommentStatus, withBanUser} from 'plugin-api/beta/client/hocs';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
import { compose } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { closeBanDialog, closeMenu } from '../actions';
|
||||
import { notify } from 'plugin-api/beta/client/actions/notification';
|
||||
import {
|
||||
connect,
|
||||
withSetCommentStatus,
|
||||
withBanUser,
|
||||
} from 'plugin-api/beta/client/hocs';
|
||||
import { getErrorMessages } from 'plugin-api/beta/client/utils';
|
||||
import BanUserDialog from '../components/BanUserDialog';
|
||||
|
||||
class BanUserDialogContainer extends React.Component {
|
||||
|
||||
class BanUserDialogContainer extends React.Component {
|
||||
banUser = async () => {
|
||||
const {
|
||||
notify,
|
||||
@@ -35,19 +37,18 @@ class BanUserDialogContainer extends React.Component {
|
||||
if (commentStatus !== 'REJECTED') {
|
||||
await setCommentStatus({
|
||||
commentId: commentId,
|
||||
status: 'REJECTED'
|
||||
status: 'REJECTED',
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
} catch (err) {
|
||||
notify('error', getErrorMessages(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<BanUserDialog
|
||||
banUser={this.banUser}
|
||||
<BanUserDialog
|
||||
banUser={this.banUser}
|
||||
showBanDialog={this.props.showBanDialog}
|
||||
closeBanDialog={this.props.closeBanDialog}
|
||||
/>
|
||||
@@ -60,23 +61,26 @@ BanUserDialogContainer.propTypes = {
|
||||
closeBanDialog: PropTypes.func,
|
||||
};
|
||||
|
||||
const mapStateToProps = ({talkPluginModerationActions: state}) => ({
|
||||
const mapStateToProps = ({ talkPluginModerationActions: state }) => ({
|
||||
showBanDialog: state.showBanDialog,
|
||||
commentId: state.commentId,
|
||||
authorId: state.authorId,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
notify,
|
||||
closeBanDialog,
|
||||
closeMenu,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
notify,
|
||||
closeBanDialog,
|
||||
closeMenu,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withSetCommentStatus,
|
||||
withBanUser,
|
||||
withBanUser
|
||||
);
|
||||
|
||||
export default enhance(BanUserDialogContainer);
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
import React from 'react';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {gql, compose} from 'react-apollo';
|
||||
import {openMenu, closeMenu} from '../actions';
|
||||
import {can} from 'plugin-api/beta/client/services';
|
||||
import {getShallowChanges} from 'plugin-api/beta/client/utils';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { gql, compose } from 'react-apollo';
|
||||
import { openMenu, closeMenu } from '../actions';
|
||||
import { can } from 'plugin-api/beta/client/services';
|
||||
import { getShallowChanges } from 'plugin-api/beta/client/utils';
|
||||
import ModerationActions from '../components/ModerationActions';
|
||||
import {connect, excludeIf, withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import { connect, excludeIf, withFragments } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class ModerationActionsContainer extends React.Component {
|
||||
|
||||
shouldComponentUpdate(nextProps) {
|
||||
|
||||
// Specifically handle `showMenuForComment` if it is the only change.
|
||||
const changes = getShallowChanges(this.props, nextProps);
|
||||
if (changes.length === 1 && changes[0] === 'showMenuForComment') {
|
||||
@@ -22,7 +20,7 @@ class ModerationActionsContainer extends React.Component {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Prevent Slot from rerendering when no props has shallowly changed.
|
||||
return changes.length !== 0;
|
||||
}
|
||||
@@ -33,37 +31,42 @@ class ModerationActionsContainer extends React.Component {
|
||||
} else {
|
||||
this.props.openMenu(this.props.comment.id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
hideMenu = () => {
|
||||
if (this.props.showMenuForComment === this.props.comment.id) {
|
||||
this.props.closeMenu();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <ModerationActions
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
comment={this.props.comment}
|
||||
toogleMenu={this.toogleMenu}
|
||||
hideMenu={this.hideMenu}
|
||||
menuVisible={this.props.showMenuForComment === this.props.comment.id}
|
||||
/>;
|
||||
return (
|
||||
<ModerationActions
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
asset={this.props.asset}
|
||||
comment={this.props.comment}
|
||||
toogleMenu={this.toogleMenu}
|
||||
hideMenu={this.hideMenu}
|
||||
menuVisible={this.props.showMenuForComment === this.props.comment.id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = ({auth, talkPluginModerationActions: state}) => ({
|
||||
const mapStateToProps = ({ auth, talkPluginModerationActions: state }) => ({
|
||||
user: auth.user,
|
||||
showMenuForComment: state.showMenuForComment,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
openMenu,
|
||||
closeMenu,
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
openMenu,
|
||||
closeMenu,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
@@ -73,13 +76,13 @@ const enhance = compose(
|
||||
me {
|
||||
id
|
||||
}
|
||||
}`
|
||||
,
|
||||
}
|
||||
`,
|
||||
asset: gql`
|
||||
fragment TalkModerationActions_asset on Asset {
|
||||
id
|
||||
}`
|
||||
,
|
||||
}
|
||||
`,
|
||||
comment: gql`
|
||||
fragment TalkModerationActions_comment on Comment {
|
||||
id
|
||||
@@ -93,8 +96,9 @@ const enhance = compose(
|
||||
}
|
||||
}
|
||||
}
|
||||
`}),
|
||||
excludeIf((props) => !can(props.user, 'MODERATE_COMMENTS')),
|
||||
`,
|
||||
}),
|
||||
excludeIf(props => !can(props.user, 'MODERATE_COMMENTS'))
|
||||
);
|
||||
|
||||
export default enhance(ModerationActionsContainer);
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
import React from 'react';
|
||||
import {compose} from 'react-apollo';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {getErrorMessages} from 'plugin-api/beta/client/utils';
|
||||
import {notify} from 'plugin-api/beta/client/actions/notification';
|
||||
import { compose } from 'react-apollo';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { getErrorMessages } from 'plugin-api/beta/client/utils';
|
||||
import { notify } from 'plugin-api/beta/client/actions/notification';
|
||||
import RejectCommentAction from '../components/RejectCommentAction';
|
||||
import {connect, withSetCommentStatus} from 'plugin-api/beta/client/hocs';
|
||||
import { connect, withSetCommentStatus } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
class RejectCommentActionContainer extends React.Component {
|
||||
|
||||
rejectComment = async () => {
|
||||
const {setCommentStatus, comment, hideMenu, notify} = this.props;
|
||||
const { setCommentStatus, comment, hideMenu, notify } = this.props;
|
||||
|
||||
try {
|
||||
await setCommentStatus({
|
||||
commentId: comment.id,
|
||||
status: 'REJECTED'
|
||||
status: 'REJECTED',
|
||||
});
|
||||
}
|
||||
catch(err) {
|
||||
} catch (err) {
|
||||
notify('error', getErrorMessages(err));
|
||||
}
|
||||
|
||||
hideMenu();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return <RejectCommentAction rejectComment={this.rejectComment}/>;
|
||||
return <RejectCommentAction rejectComment={this.rejectComment} />;
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
notify
|
||||
}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
notify,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const enhance = compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
|
||||
@@ -9,5 +9,5 @@ export default {
|
||||
commentInfoBar: [ModerationActions],
|
||||
},
|
||||
reducer,
|
||||
translations
|
||||
translations,
|
||||
};
|
||||
|
||||
@@ -1,42 +1,47 @@
|
||||
import {OPEN_MENU, CLOSE_MENU, OPEN_BAN_DIALOG, CLOSE_BAN_DIALOG} from './constants';
|
||||
import {
|
||||
OPEN_MENU,
|
||||
CLOSE_MENU,
|
||||
OPEN_BAN_DIALOG,
|
||||
CLOSE_BAN_DIALOG,
|
||||
} from './constants';
|
||||
|
||||
const initialState = {
|
||||
showMenuForComment: null,
|
||||
showBanDialog: false,
|
||||
authorId: null,
|
||||
commentId: null,
|
||||
commentStatus: null
|
||||
commentStatus: null,
|
||||
};
|
||||
|
||||
export default function reducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case OPEN_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: action.id
|
||||
};
|
||||
case CLOSE_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: null
|
||||
};
|
||||
case OPEN_BAN_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
showBanDialog: true,
|
||||
authorId: action.authorId,
|
||||
commentId: action.commentId,
|
||||
commentStatus: action.commentStatus
|
||||
};
|
||||
case CLOSE_BAN_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
showBanDialog: false,
|
||||
authorId: null,
|
||||
commentId: null,
|
||||
commentStatus: null
|
||||
};
|
||||
default :
|
||||
return state;
|
||||
case OPEN_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: action.id,
|
||||
};
|
||||
case CLOSE_MENU:
|
||||
return {
|
||||
...state,
|
||||
showMenuForComment: null,
|
||||
};
|
||||
case OPEN_BAN_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
showBanDialog: true,
|
||||
authorId: action.authorId,
|
||||
commentId: action.commentId,
|
||||
commentStatus: action.commentStatus,
|
||||
};
|
||||
case CLOSE_BAN_DIALOG:
|
||||
return {
|
||||
...state,
|
||||
showBanDialog: false,
|
||||
authorId: null,
|
||||
commentId: null,
|
||||
commentStatus: null,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants';
|
||||
import { OFFTOPIC_TOGGLE_CHECKBOX } from './constants';
|
||||
|
||||
export const toggleCheckbox = () => ({
|
||||
type: OFFTOPIC_TOGGLE_CHECKBOX
|
||||
type: OFFTOPIC_TOGGLE_CHECKBOX,
|
||||
});
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
import styles from './OffTopicCheckbox.css';
|
||||
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
export default class OffTopicCheckbox extends React.Component {
|
||||
|
||||
label = 'OFF_TOPIC';
|
||||
|
||||
componentDidMount() {
|
||||
@@ -18,28 +17,30 @@ export default class OffTopicCheckbox extends React.Component {
|
||||
this.props.unregisterHook(this.clearTagsHook);
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
const {addTag, removeTag} = this.props;
|
||||
handleChange = e => {
|
||||
const { addTag, removeTag } = this.props;
|
||||
if (e.target.checked) {
|
||||
addTag(this.label);
|
||||
} else {
|
||||
const idx = this.props.tags.indexOf(this.label);
|
||||
removeTag(idx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const checked = this.props.tags.indexOf(this.label) >= 0;
|
||||
return (
|
||||
<div className={styles.offTopic}>
|
||||
{
|
||||
!this.props.isReply ? (
|
||||
<label className={styles.offTopicLabel}>
|
||||
<input type="checkbox" onChange={this.handleChange} checked={checked}/>
|
||||
{t('off_topic')}
|
||||
</label>
|
||||
) : null
|
||||
}
|
||||
{!this.props.isReply ? (
|
||||
<label className={styles.offTopicLabel}>
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={this.handleChange}
|
||||
checked={checked}
|
||||
/>
|
||||
{t('off_topic')}
|
||||
</label>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import React from 'react';
|
||||
import styles from './OffTopicFilter.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
|
||||
export default class OffTopicFilter extends React.Component {
|
||||
|
||||
tag = 'OFF_TOPIC';
|
||||
className = 'talk-plugin-off-topic-comment';
|
||||
cn = {[this.className] : {tags: [this.tag]}};
|
||||
cn = { [this.className]: { tags: [this.tag] } };
|
||||
|
||||
handleChange = (e) => {
|
||||
handleChange = e => {
|
||||
if (e.target.checked) {
|
||||
this.props.addCommentClassName(this.cn);
|
||||
this.props.toggleCheckbox();
|
||||
} else {
|
||||
const idx = this.props.commentClassNames.findIndex((i) => i[this.className]);
|
||||
const idx = this.props.commentClassNames.findIndex(
|
||||
i => i[this.className]
|
||||
);
|
||||
this.props.removeCommentClassName(idx);
|
||||
this.props.toggleCheckbox();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import styles from './OffTopicTag.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {isTagged} from 'plugin-api/beta/client/utils';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { isTagged } from 'plugin-api/beta/client/utils';
|
||||
import cn from 'classnames';
|
||||
|
||||
export default (props) => (
|
||||
export default props => (
|
||||
<span>
|
||||
{
|
||||
isTagged(props.comment.tags, 'OFF_TOPIC') && props.depth === 0 ? (
|
||||
<span className={cn(styles.tag, 'talk-stream-comment-offtopic-tag-label')}>
|
||||
{t('off_topic')}
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
{isTagged(props.comment.tags, 'OFF_TOPIC') && props.depth === 0 ? (
|
||||
<span
|
||||
className={cn(styles.tag, 'talk-stream-comment-offtopic-tag-label')}
|
||||
>
|
||||
{t('off_topic')}
|
||||
</span>
|
||||
) : null}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {addTag, removeTag} from 'plugin-api/alpha/client/actions';
|
||||
import {commentBoxTagsSelector} from 'plugin-api/alpha/client/selectors';
|
||||
import {connect} from 'plugin-api/beta/client/hocs';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { addTag, removeTag } from 'plugin-api/alpha/client/actions';
|
||||
import { commentBoxTagsSelector } from 'plugin-api/alpha/client/selectors';
|
||||
import { connect } from 'plugin-api/beta/client/hocs';
|
||||
import OffTopicCheckbox from '../components/OffTopicCheckbox';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
tags: commentBoxTagsSelector(state)
|
||||
const mapStateToProps = state => ({
|
||||
tags: commentBoxTagsSelector(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({addTag, removeTag}, dispatch);
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators({ addTag, removeTag }, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(OffTopicCheckbox);
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import {connect} from 'plugin-api/beta/client/hocs';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import {toggleCheckbox} from '../actions';
|
||||
import {commentClassNamesSelector} from 'plugin-api/alpha/client/selectors';
|
||||
import { connect } from 'plugin-api/beta/client/hocs';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { toggleCheckbox } from '../actions';
|
||||
import { commentClassNamesSelector } from 'plugin-api/alpha/client/selectors';
|
||||
import OffTopicFilter from '../components/OffTopicFilter';
|
||||
import {
|
||||
addCommentClassName,
|
||||
removeCommentClassName
|
||||
removeCommentClassName,
|
||||
} from 'plugin-api/alpha/client/actions';
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
const mapStateToProps = state => ({
|
||||
commentClassNames: commentClassNamesSelector(state),
|
||||
checked: state.talkPluginOfftopic.checked
|
||||
checked: state.talkPluginOfftopic.checked,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
toggleCheckbox,
|
||||
addCommentClassName,
|
||||
removeCommentClassName
|
||||
removeCommentClassName,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import { gql } from 'react-apollo';
|
||||
import OffTopicTag from '../components/OffTopicTag';
|
||||
import {withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
export default withFragments({
|
||||
comment: gql`
|
||||
@@ -11,5 +11,5 @@ export default withFragments({
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
`,
|
||||
})(OffTopicTag);
|
||||
|
||||
@@ -15,6 +15,6 @@ export default {
|
||||
slots: {
|
||||
commentInputDetailArea: [OffTopicCheckbox],
|
||||
commentInfoBar: [OffTopicTag],
|
||||
viewingOptionsFilter: [OffTopicFilter]
|
||||
}
|
||||
viewingOptionsFilter: [OffTopicFilter],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import {OFFTOPIC_TOGGLE_CHECKBOX} from './constants';
|
||||
import { OFFTOPIC_TOGGLE_CHECKBOX } from './constants';
|
||||
|
||||
const initialState = {
|
||||
checked: false
|
||||
checked: false,
|
||||
};
|
||||
|
||||
export default function offTopic (state = initialState, action) {
|
||||
export default function offTopic(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case OFFTOPIC_TOGGLE_CHECKBOX: {
|
||||
return {
|
||||
...state,
|
||||
checked: !state.checked
|
||||
};
|
||||
}
|
||||
default :
|
||||
return state;
|
||||
case OFFTOPIC_TOGGLE_CHECKBOX: {
|
||||
return {
|
||||
...state,
|
||||
checked: !state.checked,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ module.exports = {
|
||||
permissions: {
|
||||
public: true,
|
||||
self: true,
|
||||
roles: []
|
||||
roles: [],
|
||||
},
|
||||
models: ['COMMENTS'],
|
||||
created_at: new Date()
|
||||
}
|
||||
]
|
||||
created_at: new Date(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import styles from './styles.css';
|
||||
import {t} from 'plugin-api/beta/client/services';
|
||||
import {ClickOutside} from 'plugin-api/beta/client/components';
|
||||
import {Icon, Button} from 'plugin-api/beta/client/components/ui';
|
||||
import { t } from 'plugin-api/beta/client/services';
|
||||
import { ClickOutside } from 'plugin-api/beta/client/components';
|
||||
import { Icon, Button } from 'plugin-api/beta/client/components/ui';
|
||||
|
||||
const name = 'talk-plugin-permalink';
|
||||
|
||||
export default class PermalinkButton extends React.Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
popoverOpen: false,
|
||||
copySuccessful: null,
|
||||
copyFailure: null
|
||||
copyFailure: null,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
this.popover.style.top = `${this.linkButton.offsetTop - 80}px`;
|
||||
|
||||
this.setState({
|
||||
popoverOpen: !this.state.popoverOpen
|
||||
popoverOpen: !this.state.popoverOpen,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
handleClickOutside = () => {
|
||||
if (this.state.popoverOpen) {
|
||||
this.setState({
|
||||
popoverOpen: false
|
||||
popoverOpen: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
copyPermalink = () => {
|
||||
this.permalinkInput.select();
|
||||
@@ -41,52 +40,56 @@ export default class PermalinkButton extends React.Component {
|
||||
document.execCommand('copy');
|
||||
this.setState({
|
||||
copySuccessful: true,
|
||||
copyFailure: null
|
||||
copyFailure: null,
|
||||
});
|
||||
} catch (err) {
|
||||
this.setState({
|
||||
copyFailure: true,
|
||||
copySuccessful: null
|
||||
copySuccessful: null,
|
||||
});
|
||||
}
|
||||
|
||||
this.timeout = window.setTimeout(() => {
|
||||
this.setState({
|
||||
copyFailure: null,
|
||||
copySuccessful: null
|
||||
copySuccessful: null,
|
||||
});
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
window.clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {copySuccessful, copyFailure, popoverOpen} = this.state;
|
||||
const {asset, comment} = this.props;
|
||||
render() {
|
||||
const { copySuccessful, copyFailure, popoverOpen } = this.state;
|
||||
const { asset, comment } = this.props;
|
||||
return (
|
||||
<ClickOutside onClickOutside={this.handleClickOutside}>
|
||||
<div className={cn(`${name}-container`, styles.container)}>
|
||||
|
||||
<button
|
||||
ref={(ref) => this.linkButton = ref}
|
||||
ref={ref => (this.linkButton = ref)}
|
||||
onClick={this.toggle}
|
||||
className={cn(`${name}-button`, styles.button)}>
|
||||
<span className='talk-plugin-permalink-button-label'>
|
||||
className={cn(`${name}-button`, styles.button)}
|
||||
>
|
||||
<span className="talk-plugin-permalink-button-label">
|
||||
{t('permalink')}
|
||||
</span>
|
||||
<Icon name="link" className={styles.icon}/>
|
||||
<Icon name="link" className={styles.icon} />
|
||||
</button>
|
||||
|
||||
<div
|
||||
ref={(ref) => this.popover = ref}
|
||||
className={cn([`${name}-popover`, styles.popover, {[styles.active]: popoverOpen}])}>
|
||||
|
||||
ref={ref => (this.popover = ref)}
|
||||
className={cn([
|
||||
`${name}-popover`,
|
||||
styles.popover,
|
||||
{ [styles.active]: popoverOpen },
|
||||
])}
|
||||
>
|
||||
<input
|
||||
className={cn(styles.input, `${name}-copy-field`)}
|
||||
type='text'
|
||||
ref={(input) => this.permalinkInput = input}
|
||||
type="text"
|
||||
ref={input => (this.permalinkInput = input)}
|
||||
defaultValue={`${asset.url}?commentId=${comment.id}`}
|
||||
readOnly
|
||||
/>
|
||||
@@ -95,15 +98,17 @@ export default class PermalinkButton extends React.Component {
|
||||
onClick={this.copyPermalink}
|
||||
className={cn([
|
||||
styles.copyButton,
|
||||
`${name}-copy-button`, {
|
||||
[styles.success]:copySuccessful,
|
||||
[styles.failure]: copyFailure
|
||||
}])}>
|
||||
`${name}-copy-button`,
|
||||
{
|
||||
[styles.success]: copySuccessful,
|
||||
[styles.failure]: copyFailure,
|
||||
},
|
||||
])}
|
||||
>
|
||||
{!copyFailure && !copySuccessful && 'Copy'}
|
||||
{copySuccessful && 'Copied'}
|
||||
{copyFailure && 'Not supported'}
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</ClickOutside>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import { gql } from 'react-apollo';
|
||||
import PermalinkButton from '../components/PermalinkButton';
|
||||
import {withFragments} from 'plugin-api/beta/client/hocs';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
export default withFragments({
|
||||
asset: gql`
|
||||
fragment TalkPermalink_Button_asset on Asset {
|
||||
url
|
||||
}
|
||||
`,
|
||||
`,
|
||||
comment: gql`
|
||||
fragment TalkPermalink_Button_comment on Comment {
|
||||
id
|
||||
}
|
||||
`
|
||||
`,
|
||||
})(PermalinkButton);
|
||||
|
||||
@@ -2,6 +2,6 @@ import PermalinkButton from './containers/PermalinkButton';
|
||||
|
||||
export default {
|
||||
slots: {
|
||||
commentActions: [PermalinkButton]
|
||||
}
|
||||
commentActions: [PermalinkButton],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import {setSort} from 'plugin-api/beta/client/actions/stream';
|
||||
import {sortOrderSelector, sortBySelector} from 'plugin-api/beta/client/selectors/stream';
|
||||
import { setSort } from 'plugin-api/beta/client/actions/stream';
|
||||
import {
|
||||
sortOrderSelector,
|
||||
sortBySelector,
|
||||
} from 'plugin-api/beta/client/selectors/stream';
|
||||
|
||||
const STORAGE_PATH = 'talkPluginRememberSort';
|
||||
|
||||
export default {
|
||||
init: async ({store, pymStorage, introspection}) => {
|
||||
|
||||
init: async ({ store, pymStorage, introspection }) => {
|
||||
// TODO: workaround as this plugin is included in any target and
|
||||
// embeds (e.g. admin), but should only be included inside the stream.
|
||||
|
||||
@@ -32,9 +34,9 @@ export default {
|
||||
|
||||
// Save sorting choice to storage if it has changed.
|
||||
if (!sort || sort.sortOrder !== sortOrder || sort.sortBy !== sortBy) {
|
||||
sort = {sortOrder, sortBy};
|
||||
sort = { sortOrder, sortBy };
|
||||
pymStorage.setItem(STORAGE_PATH, JSON.stringify(sort));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user