Merge branch 'master' of github.com:coralproject/talk into install-admin

This commit is contained in:
Belen Curcio
2017-02-07 00:16:51 -03:00
28 changed files with 847 additions and 284 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk)
A commenting platform from [The Coral Project](https://coralproject.net).
A commenting platform from [The Coral Project](https://coralproject.net). Talk enters a closed beta in March 2017, but you can download the code for our alpha here. [Read more about Talk here.](https://coralproject.net/products/talk.html)
## Contributing to Talk
+1 -1
View File
@@ -35,7 +35,7 @@ test:
deployment:
release:
tag: /[0-9]+(\.[0-9]+)*/
tag: /v[0-9]+(\.[0-9]+)*/
commands:
- bash ./scripts/deploy.sh
@@ -25,8 +25,24 @@ class EmbedLink extends Component {
}
render () {
const embedText = `<div id='coralStreamEmbed'></div><script type='text/javascript' src='${window.location.protocol}//pym.nprapps.org/pym.v1.min.js'></script><script>var pymParent = new pym.Parent('coralStreamEmbed', '${window.location.protocol}//${window.location.host}/embed/stream', {title: 'Comments'});</script>`;
const location = window.location;
const talkBaseUrl = [
location.protocol,
'//',
location.hostname,
location.port ? (`:${ window.location.port}`) : ''
].join('');
const coralJsUrl = [talkBaseUrl, '/embed.js'].join('');
const nonce = String(Math.random()).slice(2);
const streamElementId = `coral_talk_${nonce}`;
const embedText = `
<div id="${streamElementId}"></div>
<script src="${coralJsUrl}" async onload="
Coral.Talk.render(document.getElementById('${streamElementId}'), {
talk: '${talkBaseUrl}'
});
"></script>
`.trim();
return (
<div>
<h3>{this.props.title}</h3>
+1 -1
View File
@@ -50,7 +50,7 @@
"configure": {
"enable-pre-moderation": "Enable pre-moderation",
"enable-pre-moderation-text": "Moderators must approve any comment before it is published.",
"require-email-verification": "Require Email Confirmation",
"require-email-verification": "Require Email Verification",
"require-email-verification-text": "New Users must verify their email before commenting",
"include-comment-stream": "Include Comment Stream Description for Readers.",
"include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.",
+3 -1
View File
@@ -20,6 +20,7 @@ import CommentBox from 'coral-plugin-commentbox/CommentBox';
import UserBox from 'coral-sign-in/components/UserBox';
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
import ChangeDisplayNameContainer from '../../coral-sign-in/containers/ChangeDisplayNameContainer';
import SettingsContainer from 'coral-settings/containers/SettingsContainer';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
@@ -130,7 +131,8 @@ class Embed extends Component {
</div>
: <p>{asset.settings.closedMessage}</p>
}
{!loggedIn && <SignInContainer offset={signInOffset}/>}
{!loggedIn && <SignInContainer requireEmailConfirmation={asset.settings.requireEmailConfirmation} offset={signInOffset}/>}
{loggedIn && user && <ChangeDisplayNameContainer loggedIn={loggedIn} offset={signInOffset} user={user} />}
<Stream
refetch={refetch}
addNotification={this.props.addNotification}
File diff suppressed because one or more lines are too long
+48 -17
View File
@@ -8,6 +8,28 @@ import coralApi, {base} from '../helpers/response';
export const showSignInDialog = (offset = 0) => ({type: actions.SHOW_SIGNIN_DIALOG, offset});
export const hideSignInDialog = () => ({type: actions.HIDE_SIGNIN_DIALOG});
export const createDisplayNameRequest = () => ({type: actions.CREATE_DISPLAYNAME_REQUEST});
export const showCreateDisplayNameDialog = () => ({type: actions.SHOW_CREATEDISPLAYNAME_DIALOG});
export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISPLAYNAME_DIALOG});
const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS});
const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error});
export const updateDisplayName = displayName => ({type: actions.UPDATE_DISPLAYNAME, displayName});
export const createDisplayName = (userId, formData) => dispatch => {
dispatch(createDisplayNameRequest());
coralApi(`/users/${userId}/displayname`, {method: 'POST', body: formData})
.then((user) => {
dispatch(createDisplayNameSuccess());
dispatch(hideCreateDisplayNameDialog());
dispatch(updateDisplayName(user));
})
.catch(error => {
dispatch(createDisplayNameFailure(lang.t(`error.${error.message}`)));
});
};
export const changeView = view => dispatch =>
dispatch({
type: actions.CHANGE_VIEW,
@@ -21,7 +43,6 @@ export const cleanState = () => ({type: actions.CLEAN_STATE});
const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST});
const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin});
const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error});
const emailConfirmError = () => ({type: actions.EMAIL_CONFIRM_ERROR});
export const fetchSignIn = (formData) => (dispatch) => {
dispatch(signInRequest());
@@ -36,7 +57,6 @@ export const fetchSignIn = (formData) => (dispatch) => {
// the user might not have a valid email. prompt the user user re-request the confirmation email
dispatch(signInFailure(lang.t('error.emailNotVerified', error.metadata)));
dispatch(emailConfirmError());
} else {
// invalid credentials
@@ -60,6 +80,19 @@ export const fetchSignInFacebook = () => dispatch => {
);
};
// Sign Up Facebook
const signUpFacebookRequest = () => ({type: actions.FETCH_SIGNUP_FACEBOOK_REQUEST});
export const fetchSignUpFacebook = () => dispatch => {
dispatch(signUpFacebookRequest());
window.open(
`${base}/auth/facebook`,
'Continue with Facebook',
'menubar=0,resizable=0,width=500,height=500,top=200,left=500'
);
};
export const facebookCallback = (err, data) => dispatch => {
if (err) {
signInFacebookFailure(err);
@@ -69,6 +102,7 @@ export const facebookCallback = (err, data) => dispatch => {
const user = JSON.parse(data);
dispatch(signInFacebookSuccess(user));
dispatch(hideSignInDialog());
dispatch(showCreateDisplayNameDialog());
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
@@ -81,15 +115,12 @@ const signUpRequest = () => ({type: actions.FETCH_SIGNUP_REQUEST});
const signUpSuccess = user => ({type: actions.FETCH_SIGNUP_SUCCESS, user});
const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
export const fetchSignUp = formData => (dispatch) => {
export const fetchSignUp = (formData, redirectUri) => (dispatch) => {
dispatch(signUpRequest());
coralApi('/users', {method: 'POST', body: formData})
coralApi('/users', {method: 'POST', body: formData, headers: {'X-Pym-Url': redirectUri}})
.then(({user}) => {
dispatch(signUpSuccess(user));
setTimeout(() =>{
dispatch(changeView('SIGNIN'));
}, 3000);
})
.catch(error => {
dispatch(signUpFailure(lang.t(`error.${error.message}`)));
@@ -150,20 +181,20 @@ export const checkLogin = () => dispatch => {
});
};
const confirmEmailRequest = () => ({type: actions.CONFIRM_EMAIL_REQUEST});
const confirmEmailSuccess = () => ({type: actions.CONFIRM_EMAIL_SUCCESS});
const confirmEmailFailure = () => ({type: actions.CONFIRM_EMAIL_FAILURE});
const verifyEmailRequest = () => ({type: actions.VERIFY_EMAIL_REQUEST});
const verifyEmailSuccess = () => ({type: actions.VERIFY_EMAIL_SUCCESS});
const verifyEmailFailure = () => ({type: actions.VERIFY_EMAIL_FAILURE});
export const requestConfirmEmail = email => dispatch => {
dispatch(confirmEmailRequest());
return coralApi('/users/resend-confirm', {method: 'POST', body: {email}})
export const requestConfirmEmail = (email, redirectUri) => dispatch => {
dispatch(verifyEmailRequest());
return coralApi('/users/resend-verify', {method: 'POST', body: {email}, headers: {'X-Pym-Url': redirectUri}})
.then(() => {
dispatch(confirmEmailSuccess());
dispatch(verifyEmailSuccess());
})
.catch(err => {
console.log('failed to send email confirmation', err);
console.log('failed to send email verification', err);
// email might have already been confirmed
dispatch(confirmEmailFailure());
// email might have already been verifyed
dispatch(verifyEmailFailure());
});
};
+12 -4
View File
@@ -4,6 +4,13 @@ export const CLEAN_STATE = 'CLEAN_STATE';
export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG';
export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG';
export const CREATE_DISPLAYNAME_REQUEST = 'CREATE_DISPLAYNAME_REQUEST';
export const CREATEDISPLAYNAME_SUCCESS = 'CREATEDISPLAYNAME_SUCCESS';
export const CREATEDISPLAYNAME_FAILURE = 'CREATEDISPLAYNAME_FAILURE';
export const CREATE_DISPLAYNAME = 'CREATE_DISPLAYNAME';
export const SHOW_CREATEDISPLAYNAME_DIALOG = 'SHOW_CREATEDISPLAYNAME_DIALOG';
export const HIDE_CREATEDISPLAYNAME_DIALOG = 'HIDE_CREATEDISPLAYNAME_DIALOG';
export const FETCH_SIGNUP_REQUEST = 'FETCH_SIGNUP_REQUEST';
export const FETCH_SIGNUP_FAILURE = 'FETCH_SIGNUP_FAILURE';
export const FETCH_SIGNUP_SUCCESS = 'FETCH_SIGNUP_SUCCESS';
@@ -16,6 +23,7 @@ export const FETCH_SIGNIN_FACEBOOK_REQUEST = 'FETCH_SIGNIN_FACEBOOK_REQUEST';
export const FETCH_SIGNIN_FACEBOOK_FAILURE = 'FETCH_SIGNIN_FACEBOOK_FAILURE';
export const FETCH_SIGNIN_FACEBOOK_SUCCESS = 'FETCH_SIGNIN_FACEBOOK_SUCCESS';
export const FETCH_SIGNUP_FACEBOOK_REQUEST = 'FETCH_SIGNUP_FACEBOOK_REQUEST';
export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST';
export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS';
export const FETCH_FORGOT_PASSWORD_FAILURE = 'FETCH_FORGOT_PASSWORD_FAILURE';
@@ -33,7 +41,7 @@ export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE';
export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN';
export const EMAIL_CONFIRM_ERROR = 'EMAIL_CONFIRM_ERROR';
export const CONFIRM_EMAIL_REQUEST = 'CONFIRM_EMAIL_REQUEST';
export const CONFIRM_EMAIL_SUCCESS = 'CONFIRM_EMAIL_SUCCESS';
export const CONFIRM_EMAIL_FAILURE = 'CONFIRM_EMAIL_FAILURE';
export const VERIFY_EMAIL_REQUEST = 'VERIFY_EMAIL_REQUEST';
export const VERIFY_EMAIL_SUCCESS = 'VERIFY_EMAIL_SUCCESS';
export const VERIFY_EMAIL_FAILURE = 'VERIFY_EMAIL_FAILURE';
export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME';
+1
View File
@@ -5,3 +5,4 @@ export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST';
export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS';
export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE';
export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS';
export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME';
+2 -1
View File
@@ -14,7 +14,8 @@ const buildOptions = (inputOptions = {}) => {
_csrf: csurfDOM ? csurfDOM.content : false
};
const options = Object.assign({}, defaultOptions, inputOptions);
let options = Object.assign({}, defaultOptions, inputOptions);
options.headers = Object.assign({}, defaultOptions.headers, inputOptions.headers);
if (options._csrf) {
switch (options.method.toLowerCase()) {
+41 -15
View File
@@ -7,14 +7,16 @@ const initialState = Map({
isAdmin: false,
user: null,
showSignInDialog: false,
showCreateDisplayNameDialog: false,
view: 'SIGNIN',
error: '',
passwordRequestSuccess: null,
passwordRequestFailure: null,
emailConfirmationFailure: false,
emailConfirmationLoading: false,
emailConfirmationSuccess: false,
successSignUp: false
emailVerificationFailure: false,
emailVerificationLoading: false,
emailVerificationSuccess: false,
successSignUp: false,
fromSignUp: false
});
const purge = user => {
@@ -36,11 +38,26 @@ export default function auth (state = initialState, action) {
error: '',
passwordRequestFailure: null,
passwordRequestSuccess: null,
emailConfirmationFailure: false,
emailConfirmationSuccess: false,
emailConfirmationLoading: false,
emailVerificationFailure: false,
emailVerificationSuccess: false,
emailVerificationLoading: false,
successSignUp: false
}));
case actions.SHOW_CREATEDISPLAYNAME_DIALOG :
return state
.set('showCreateDisplayNameDialog', true);
case actions.HIDE_CREATEDISPLAYNAME_DIALOG :
return state.merge(Map({
showCreateDisplayNameDialog: false
}));
case actions.CREATEDISPLAYNAME_SUCCESS :
return state.merge(Map({
showCreateDisplayNameDialog: false,
error: ''
}));
case actions.CREATEDISPLAYNAME_FAILURE :
return state
.set('error', action.error);
case actions.CHANGE_VIEW :
return state
.set('error', '')
@@ -72,6 +89,12 @@ export default function auth (state = initialState, action) {
.set('isLoading', false)
.set('error', action.error)
.set('user', null);
case actions.FETCH_SIGNUP_FACEBOOK_REQUEST:
return state
.set('fromSignUp', true);
case actions.FETCH_SIGNIN_FACEBOOK_REQUEST:
return state
.set('fromSignUp', false);
case actions.FETCH_SIGNIN_FACEBOOK_SUCCESS:
return state
.set('user', purge(action.user))
@@ -107,16 +130,19 @@ export default function auth (state = initialState, action) {
return state
.set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!')
.set('passwordRequestSuccess', null);
case actions.EMAIL_CONFIRM_ERROR:
case actions.UPDATE_DISPLAYNAME:
return state
.set('emailConfirmationFailure', true)
.set('emailConfirmationLoading', false);
case actions.CONFIRM_EMAIL_REQUEST:
return state.set('emailConfirmationLoading', true);
case actions.CONFIRM_EMAIL_SUCCESS:
.set('user', purge(action.displayName));
case actions.VERIFY_EMAIL_FAILURE:
return state
.set('emailConfirmationSuccess', true)
.set('emailConfirmationLoading', false);
.set('emailVerificationFailure', true)
.set('emailVerificationLoading', false);
case actions.VERIFY_EMAIL_REQUEST:
return state.set('emailVerificationLoading', true);
case actions.VERIFY_EMAIL_SUCCESS:
return state
.set('emailVerificationSuccess', true)
.set('emailVerificationLoading', false);
default :
return state;
}
@@ -0,0 +1,48 @@
import React from 'react';
import FormField from 'coral-ui/components/FormField';
import Alert from './Alert';
import Button from 'coral-ui/components/Button';
import {Dialog} from 'coral-ui';
import styles from './styles.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSubmitDisplayName, handleChange, ...props}) => (
<Dialog
className={styles.dialog}
id="createDisplayNameDialog"
open={open}
style={{
position: 'relative',
top: offset !== 0 && offset
}}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h1>
{lang.t('createdisplay.writeyourusername')}
</h1>
</div>
<div>
<label htmlFor="displayName">{lang.t('createdisplay.yourusername')}</label>
{ props.auth.error && <Alert>{props.auth.error}</Alert> }
<form id="saveDisplayName" onSubmit={handleSubmitDisplayName}>
<FormField
id="displayName"
type="string"
label={lang.t('createdisplay.displayName')}
value={formData.displayName}
onChange={handleChange}
/>
{ props.errors.displayName && <span className={styles.hint}> {lang.t('createdisplay.specialCharacters')} </span> }
<div className={styles.action}>
<Button id="save" type="submit" className={styles.saveButton}>{lang.t('createdisplay.save')}</Button>
</div>
</form>
</div>
</div>
</Dialog>
);
export default CreateDisplayNameDialog;
@@ -17,12 +17,7 @@ const SignDialog = ({open, view, handleClose, offset, ...props}) => (
}}>
<span className={styles.close} onClick={handleClose}>×</span>
{view === 'SIGNIN' && <SignInContent {...props} />}
{
view === 'SIGNUP' && <SignUpContent
emailConfirmationLoading={props.emailConfirmationLoading}
emailConfirmationSuccess={props.emailConfirmationSuccess}
{...props} />
}
{view === 'SIGNUP' && <SignUpContent {...props} />}
{view === 'FORGOT' && <ForgotContent {...props} />}
</Dialog>
);
@@ -10,35 +10,28 @@ const SignInContent = ({
handleChange,
handleChangeEmail,
emailToBeResent,
handleResendConfirmation,
emailConfirmationLoading,
emailConfirmationSuccess,
handleResendVerification,
emailVerificationLoading,
emailVerificationSuccess,
formData,
...props
changeView,
handleSignIn,
auth,
fetchSignInFacebook
}) => {
return (
<div>
<div className={styles.header}>
<h1>
{props.auth.emailConfirmationFailure ? lang.t('signIn.emailConfirmCTA') : lang.t('signIn.signIn')}
{auth.emailVerificationFailure ? lang.t('signIn.emailVerifyCTA') : lang.t('signIn.signIn')}
</h1>
</div>
<div className={styles.socialConnections}>
<Button cStyle="facebook" onClick={props.fetchSignInFacebook} full>
{lang.t('signIn.facebookSignIn')}
</Button>
</div>
<div className={styles.separator}>
<h1>
{lang.t('signIn.or')}
</h1>
</div>
{ props.auth.error && <Alert>{props.auth.error}</Alert> }
{ auth.error && <Alert>{auth.error}</Alert> }
{
props.auth.emailConfirmationFailure
? <form onSubmit={handleResendConfirmation}>
<p>{lang.t('signIn.requestNewConfirmEmail')}</p>
auth.emailVerificationFailure
? <form onSubmit={handleResendVerification}>
<p>{lang.t('signIn.requestNewVerifyEmail')}</p>
<FormField
id="confirm-email"
type="email"
@@ -46,41 +39,53 @@ const SignInContent = ({
value={emailToBeResent}
onChange={handleChangeEmail} />
<Button id='resendConfirmEmail' type='submit' cStyle='black' full>Send Email</Button>
{emailConfirmationLoading && <Spinner />}
{emailConfirmationSuccess && <Success />}
{emailVerificationLoading && <Spinner />}
{emailVerificationSuccess && <Success />}
</form>
: <form onSubmit={props.handleSignIn}>
<FormField
id="email"
type="email"
label={lang.t('signIn.email')}
value={formData.email}
onChange={handleChange}
/>
<FormField
id="password"
type="password"
label={lang.t('signIn.password')}
value={formData.password}
onChange={handleChange}
/>
<div className={styles.action}>
{
!props.auth.isLoading ?
<Button id='coralLogInButton' type="submit" cStyle="black" className={styles.signInButton} full>
{lang.t('signIn.signIn')}
</Button>
:
<Spinner />
}
: <div>
<div className={styles.socialConnections}>
<Button cStyle="facebook" onClick={fetchSignInFacebook} full>
{lang.t('signIn.facebookSignIn')}
</Button>
</div>
</form>
<div className={styles.separator}>
<h1>
{lang.t('signIn.or')}
</h1>
</div>
<form onSubmit={handleSignIn}>
<FormField
id="email"
type="email"
label={lang.t('signIn.email')}
value={formData.email}
onChange={handleChange}
/>
<FormField
id="password"
type="password"
label={lang.t('signIn.password')}
value={formData.password}
onChange={handleChange}
/>
<div className={styles.action}>
{
!auth.isLoading ?
<Button id='coralLogInButton' type="submit" cStyle="black" className={styles.signInButton} full>
{lang.t('signIn.signIn')}
</Button>
:
<Spinner />
}
</div>
</form>
</div>
}
<div className={styles.footer}>
<span><a onClick={() => props.changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')}</a></span>
<span><a onClick={() => changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')}</a></span>
<span>
{lang.t('signIn.needAnAccount')}
<a onClick={() => props.changeView('SIGNUP')} id='coralRegister'>
<a onClick={() => changeView('SIGNUP')} id='coralRegister'>
{lang.t('signIn.register')}
</a>
</span>
@@ -90,9 +95,17 @@ const SignInContent = ({
};
SignInContent.propTypes = {
emailConfirmationLoading: PropTypes.bool.isRequired,
emailConfirmationSuccess: PropTypes.bool.isRequired,
handleResendConfirmation: PropTypes.func.isRequired,
auth: PropTypes.shape({
isLoading: PropTypes.bool.isRequired,
error: PropTypes.object,
emailVerificationFailure: PropTypes.bool
}).isRequired,
fetchSignInFacebook: PropTypes.func.isRequired,
handleSignIn: PropTypes.func.isRequired,
changeView: PropTypes.func.isRequired,
emailVerificationLoading: PropTypes.bool.isRequired,
emailVerificationSuccess: PropTypes.bool.isRequired,
handleResendVerification: PropTypes.func.isRequired,
handleChangeEmail: PropTypes.func.isRequired,
emailToBeResent: PropTypes.string.isRequired
};
+142 -77
View File
@@ -1,4 +1,4 @@
import React from 'react';
import React, {PropTypes} from 'react';
import Alert from './Alert';
import {Button, FormField, Spinner, Success} from 'coral-ui';
import styles from './styles.css';
@@ -6,83 +6,148 @@ import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const SignUpContent = ({handleChange, formData, ...props}) => (
<div>
<div className={styles.header}>
<h1>
{lang.t('signIn.signUp')}
</h1>
</div>
<div className={styles.socialConnections}>
<Button cStyle="facebook" onClick={props.fetchSignInFacebook} full>
{lang.t('signIn.facebookSignUp')}
</Button>
</div>
<div className={styles.separator}>
<h1>
{lang.t('signIn.or')}
</h1>
</div>
{ props.auth.error && <Alert>{props.auth.error}</Alert> }
<form onSubmit={props.handleSignUp}>
<FormField
id="email"
type="email"
label={lang.t('signIn.email')}
value={formData.email}
showErrors={props.showErrors}
errorMsg={props.errors.email}
onChange={handleChange}
/>
<FormField
id="displayName"
type="text"
label={lang.t('signIn.displayName')}
value={formData.displayName}
showErrors={props.showErrors}
errorMsg={props.errors.displayName}
onChange={handleChange}
/>
<FormField
id="password"
type="password"
label={lang.t('signIn.password')}
value={formData.password}
showErrors={props.showErrors}
errorMsg={props.errors.password}
onChange={handleChange}
minLength="8"
/>
{ props.errors.password && <span className={styles.hint}> Password must be at least 8 characters. </span> }
<FormField
id="confirmPassword"
type="password"
label={lang.t('signIn.confirmPassword')}
value={formData.confirmPassword}
showErrors={props.showErrors}
errorMsg={props.errors.confirmPassword}
onChange={handleChange}
minLength="8"
/>
<div className={styles.action}>
{ !props.auth.isLoading && !props.auth.successSignUp && (
<Button type="submit" cStyle="black" id='coralSignUpButton' className={styles.signInButton} full>
class SignUpContent extends React.Component {
constructor (props) {
super(props);
this.successfulSignup = false;
}
static propTypes = {
emailVerificationEnabled: PropTypes.bool.isRequired,
fetchSignUpFacebook: PropTypes.func.isRequired,
changeView: PropTypes.func.isRequired,
handleSignUp: PropTypes.func.isRequired,
showErrors: PropTypes.bool,
errors: PropTypes.shape({
email: PropTypes.string,
displayName: PropTypes.string,
password: PropTypes.string,
confirmPassword: PropTypes.string,
}),
formData: PropTypes.shape({
email: PropTypes.string,
displayName: PropTypes.string,
password: PropTypes.string,
confirmPassword: PropTypes.string
})
}
render () {
const {
handleChange,
formData,
emailVerificationEnabled,
auth,
errors,
showErrors,
changeView,
handleSignUp,
fetchSignUpFacebook} = this.props;
const beforeSignup = !auth.isLoading && !auth.successSignUp;
const successfulSignup = !auth.isLoading && auth.successSignUp;
// the first time we render a successfulSignup, trigger a timer
if ((this.successfulSignup ^ successfulSignup) && !emailVerificationEnabled) {
setTimeout(() => {
changeView('SIGNIN');
}, 1000);
this.successfulSignup = true;
}
return (
<div>
<div className={styles.header}>
<h1>
{lang.t('signIn.signUp')}
</Button>
)}
{ props.auth.isLoading && <Spinner /> }
{ !props.auth.isLoading && props.auth.successSignUp && <Success /> }
</h1>
</div>
{ auth.error && <Alert>{auth.error}</Alert> }
{ beforeSignup &&
<div>
<div className={styles.socialConnections}>
<Button cStyle="facebook" onClick={fetchSignUpFacebook} full>
{lang.t('signIn.facebookSignUp')}
</Button>
</div>
<div className={styles.separator}>
<h1>
{lang.t('signIn.or')}
</h1>
</div>
<form onSubmit={handleSignUp}>
<FormField
id="email"
type="email"
label={lang.t('signIn.email')}
value={formData.email}
showErrors={showErrors}
errorMsg={errors.email}
onChange={handleChange}
/>
<FormField
id="displayName"
type="text"
label={lang.t('signIn.displayName')}
value={formData.displayName}
showErrors={showErrors}
errorMsg={errors.displayName}
onChange={handleChange}
/>
<FormField
id="password"
type="password"
label={lang.t('signIn.password')}
value={formData.password}
showErrors={showErrors}
errorMsg={errors.password}
onChange={handleChange}
minLength="8"
/>
{ errors.password && <span className={styles.hint}> Password must be at least 8 characters. </span> }
<FormField
id="confirmPassword"
type="password"
label={lang.t('signIn.confirmPassword')}
value={formData.confirmPassword}
showErrors={showErrors}
errorMsg={errors.confirmPassword}
onChange={handleChange}
minLength="8"
/>
<div className={styles.action}>
<Button type="submit" cStyle="black" id='coralSignUpButton' className={styles.signInButton} full>
{lang.t('signIn.signUp')}
</Button>
{ auth.isLoading && <Spinner /> }
</div>
</form>
</div>
}
{
successfulSignup &&
<div>
<Success />
{
emailVerificationEnabled &&
<p>{lang.t('signIn.verifyEmail')}<br /><br />{lang.t('signIn.verifyEmail2')}</p>
}
</div>
}
<div className={styles.footer}>
<span>
{lang.t('signIn.alreadyHaveAnAccount')}
<a id="coralSignInViewTrigger" onClick={() => changeView('SIGNIN')}>
{lang.t('signIn.signIn')}
</a>
</span>
</div>
</div>
</form>
<div className={styles.footer}>
<span>
{lang.t('signIn.alreadyHaveAnAccount')}
<a onClick={() => props.changeView('SIGNIN')}>
{lang.t('signIn.signIn')}
</a>
</span>
</div>
</div>
);
);
}
}
export default SignUpContent;
@@ -0,0 +1,135 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
import CreateDisplayNameDialog from '../components/CreateDisplayNameDialog';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
import {
showCreateDisplayNameDialog,
hideCreateDisplayNameDialog,
invalidForm,
validForm,
createDisplayName
} from '../../coral-framework/actions/auth';
class ChangeDisplayNameContainer extends Component {
initialState = {
formData: {
displayName: '',
},
errors: {},
showErrors: false
};
constructor(props) {
super(props);
this.state = this.initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmitDisplayName = this.handleSubmitDisplayName.bind(this);
this.handleClose = this.handleClose.bind(this);
this.addError = this.addError.bind(this);
}
handleChange(e) {
const {name, value} = e.target;
this.setState(state => ({
...state,
formData: {
...state.formData,
[name]: value
}
}), () => {
this.validation(name, value);
});
}
addError(name, error) {
return this.setState(state => ({
errors: {
...state.errors,
[name]: error
}
}));
}
validation(name, value) {
const {addError} = this;
if (!value.length) {
addError(name, lang.t('createdisplay.requiredField'));
} else if (!validate[name](value)) {
addError(name, errorMsj[name]);
} else {
const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line
// Removes Error
this.setState(state => ({...state, errors}));
}
}
isCompleted() {
const {formData} = this.state;
return !Object.keys(formData).filter(prop => !formData[prop].length).length;
}
displayErrors(show = true) {
this.setState({showErrors: show});
}
handleSubmitDisplayName(e) {
e.preventDefault();
const {errors} = this.state;
const {validForm, invalidForm} = this.props;
this.displayErrors();
if (this.isCompleted() && !Object.keys(errors).length) {
this.props.createDisplayName(this.props.user.id, this.state.formData);
validForm();
} else {
invalidForm(lang.t('createdisplay.checkTheForm'));
}
}
handleClose() {
this.props.hideCreateDisplayNameDialog();
}
render() {
const {loggedIn, auth, offset} = this.props;
return (
<div>
<CreateDisplayNameDialog
open={auth.showCreateDisplayNameDialog && auth.fromSignUp}
offset={offset}
handleClose={this.handleClose}
loggedIn={loggedIn}
handleSubmitDisplayName={this.handleSubmitDisplayName}
{...this}
{...this.state}
{...this.props}
/>
</div>
);
}
}
const mapStateToProps = state => ({
auth: state.auth.toJS()
});
const mapDispatchToProps = dispatch => ({
createDisplayName: (userid, formData) => dispatch(createDisplayName(userid, formData)),
showCreateDisplayNameDialog: () => dispatch(showCreateDisplayNameDialog()),
hideCreateDisplayNameDialog: () => dispatch(hideCreateDisplayNameDialog()),
invalidForm: error => dispatch(invalidForm(error)),
validForm: () => dispatch(validForm())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChangeDisplayNameContainer);
@@ -1,4 +1,4 @@
import React, {Component} from 'react';
import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import SignDialog from '../components/SignDialog';
import Button from 'coral-ui/components/Button';
@@ -6,6 +6,7 @@ import validate from 'coral-framework/helpers/validate';
import errorMsj from 'coral-framework/helpers/error';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
import {pym} from 'coral-framework';
const lang = new I18n(translations);
import {
@@ -15,6 +16,7 @@ import {
showSignInDialog,
hideSignInDialog,
fetchSignInFacebook,
fetchSignUpFacebook,
fetchForgotPassword,
requestConfirmEmail,
facebookCallback,
@@ -41,12 +43,16 @@ class SignInContainer extends Component {
this.state = this.initialState;
this.handleChange = this.handleChange.bind(this);
this.handleChangeEmail = this.handleChangeEmail.bind(this);
this.handleResendConfirmation = this.handleResendConfirmation.bind(this);
this.handleResendVerification = this.handleResendVerification.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.handleSignIn = this.handleSignIn.bind(this);
this.addError = this.addError.bind(this);
}
static propTypes = {
requireEmailConfirmation: PropTypes.bool.isRequired
}
componentWillMount () {
this.props.checkLogin();
}
@@ -79,9 +85,9 @@ class SignInContainer extends Component {
this.setState({emailToBeResent: value});
}
handleResendConfirmation(e) {
handleResendVerification(e) {
e.preventDefault();
this.props.requestConfirmEmail(this.state.emailToBeResent)
this.props.requestConfirmEmail(this.state.emailToBeResent, pym.parentUrl || location.href)
.then(() => {
setTimeout(() => {
@@ -132,7 +138,7 @@ class SignInContainer extends Component {
const {fetchSignUp, validForm, invalidForm} = this.props;
this.displayErrors();
if (this.isCompleted() && !Object.keys(errors).length) {
fetchSignUp(this.state.formData);
fetchSignUp(this.state.formData, pym.parentUrl || location.href);
validForm();
} else {
invalidForm(lang.t('signIn.checkTheForm'));
@@ -145,8 +151,9 @@ class SignInContainer extends Component {
}
render() {
const {auth, showSignInDialog, noButton, offset} = this.props;
const {emailConfirmationLoading, emailConfirmationSuccess} = auth;
const {auth, showSignInDialog, noButton, offset, requireEmailConfirmation} = this.props;
const {emailVerificationLoading, emailVerificationSuccess} = auth;
return (
<div>
{!noButton && <Button id='coralSignInButton' onClick={showSignInDialog} full>
@@ -156,8 +163,9 @@ class SignInContainer extends Component {
open={auth.showSignInDialog}
view={auth.view}
offset={offset}
emailConfirmationLoading={emailConfirmationLoading}
emailConfirmationSuccess={emailConfirmationSuccess}
emailVerificationEnabled={requireEmailConfirmation}
emailVerificationLoading={emailVerificationLoading}
emailVerificationSuccess={emailVerificationSuccess}
{...this}
{...this.state}
{...this.props}
@@ -174,11 +182,12 @@ const mapStateToProps = state => ({
const mapDispatchToProps = dispatch => ({
checkLogin: () => dispatch(checkLogin()),
facebookCallback: (err, data) => dispatch(facebookCallback(err, data)),
fetchSignUp: formData => dispatch(fetchSignUp(formData)),
fetchSignUp: (formData, url) => dispatch(fetchSignUp(formData, url)),
fetchSignIn: formData => dispatch(fetchSignIn(formData)),
fetchSignInFacebook: () => dispatch(fetchSignInFacebook()),
fetchSignUpFacebook: () => dispatch(fetchSignUpFacebook()),
fetchForgotPassword: formData => dispatch(fetchForgotPassword(formData)),
requestConfirmEmail: email => dispatch(requestConfirmEmail(email)),
requestConfirmEmail: (email, url) => dispatch(requestConfirmEmail(email, url)),
showSignInDialog: () => dispatch(showSignInDialog()),
changeView: view => dispatch(changeView(view)),
handleClose: () => dispatch(hideSignInDialog()),
+30 -6
View File
@@ -1,8 +1,10 @@
export default {
en: {
'signIn': {
emailConfirmCTA: 'Please verify your email address.',
requestNewConfirmEmail: 'Request another email:',
emailVerifyCTA: 'Please verify your email address.',
requestNewVerifyEmail: 'Request another email:',
verifyEmail: 'Thank you for creating an account! We sent an email to the address you provided to verify your account.',
verifyEmail2: 'You must verify your account before engaging with the community.',
notYou: 'Not you?',
loggedInAs: 'Logged in as',
facebookSignIn: 'Sign in with Facebook',
@@ -26,12 +28,24 @@ export default {
passwordsDontMatch: 'Passwords don\'t match.',
specialCharacters: 'Display names can contain letters, numbers and _ only',
checkTheForm: 'Invalid Form. Please, check the fields'
}
},
'createdisplay': {
writeyourusername: 'Write your username',
yourusername: 'Your username is publicly visible on all comments you post. A username is needed before you can post your first comment.',
displayName: 'Display Name',
save: 'Save',
requiredField: 'Required field',
errorCreate: 'Error when changing display name',
checkTheForm: 'Invalid Form. Please, check the fields',
specialCharacters: 'Display names can contain letters, numbers and _ only'
},
},
es: {
'signIn': {
emailConfirmCTA: 'Por favor verifique su correo electronico.',
requestNewConfirmEmail: 'Enviar otro correo:',
emailVerifyCTA: 'Por favor verifique su correo electronico.',
requestNewVerifyEmail: 'Enviar otro correo:',
verifyEmail: '¡Gracias por crear una cuenta! Le enviamos un correo a la dirección que dio para verificar su cuenta.',
verifyEmail2: 'Debe verificarla antes de poder involucrarse en la comunidad.',
notYou: 'No eres tu?',
loggedInAs: 'Entraste como',
facebookSignIn: 'Entrar con Facebook',
@@ -55,6 +69,16 @@ export default {
passwordsDontMatch: 'Las contraseñas no coinciden',
specialCharacters: 'Los nombres pueden contener letras, números y _',
checkTheForm: 'Formulario Inválido. Por favor, completa los campos'
}
},
'createdisplay': {
writeyourusername: 'Escribe tu nombre',
yourusername: 'Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.',
displayName: 'Nombre a mostrar',
save: 'Guardar',
requiredField: 'Campo necesario',
errorCreate: 'Hubo un error al cambiar el nombre de usuario',
checkTheForm: 'Formulario Invalido. Por favor, verifica los campos',
specialCharacters: 'Sólo pueden contener letras, números y _'
},
}
};
+1 -1
View File
@@ -16,7 +16,7 @@ router.get('/', authorization.needed(), (req, res, next) => {
// POST /email/confirm takes the password confirmation token available as a
// payload parameter and if it verifies, it updates the confirmed_at date on the
// local profile.
router.post('/email/confirm', (req, res, next) => {
router.post('/email/verify', (req, res, next) => {
const {
token
+11 -3
View File
@@ -59,6 +59,14 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next)
.catch(next);
});
router.post('/:user_id/displayname', authorization.needed(), (req, res, next) => {
UsersService.setDisplayName(req.params.user_id, req.body.displayName)
.then((user) => {
res.status(201).json(user);
})
.catch(next);
});
router.post('/:user_id/email', authorization.needed('admin'), (req, res, next) => {
UsersService.findById(req.params.user_id)
.then(user => {
@@ -112,7 +120,7 @@ const SendEmailConfirmation = (app, userID, email, referer) => UsersService
// create a local user.
router.post('/', (req, res, next) => {
const {email, password, displayName} = req.body;
const redirectUri = req.header('Referer');
const redirectUri = req.header('X-Pym-Url') || req.header('Referer');
UsersService
.createLocalUser(email, password, displayName)
@@ -160,9 +168,9 @@ router.post('/:user_id/actions', authorization.needed(), (req, res, next) => {
});
// trigger an email confirmation re-send by a new user
router.post('/resend-confirm', (req, res, next) => {
router.post('/resend-verify', (req, res, next) => {
const {email} = req.body;
const redirectUri = req.header('Referer');
const redirectUri = req.header('X-Pym-Url') || req.header('Referer');
if (!email) {
return next(errors.ErrMissingEmail);
+2
View File
@@ -1,9 +1,11 @@
const express = require('express');
const path = require('path');
const router = express.Router();
router.use('/api/v1', require('./api'));
router.use('/admin', require('./admin'));
router.use('/embed', require('./embed'));
router.get('/embed.js', (req, res) => res.sendFile(path.join(__dirname, '../client/coral-embed/index.js')));
router.use('/assets', require('./assets'));
router.get('/', (req, res) => {
+4 -4
View File
@@ -8,11 +8,11 @@ docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
deploy_tag() {
# Find our individual versions from the tags
if [ -n "$(echo $CIRCLE_TAG | grep -E '.*\..*\..*')" ]
if [ -n "$(echo $CIRCLE_TAG | grep -E 'v.*\..*\..*')" ]
then
major=$(echo $CIRCLE_TAG | cut -d. -f1)
minor=$(echo $CIRCLE_TAG | cut -d. -f2)
patch=$(echo $CIRCLE_TAG | cut -d. -f3)
major=$(echo ${CIRCLE_TAG//v} | cut -d. -f1)
minor=$(echo ${CIRCLE_TAG//v} | cut -d. -f2)
patch=$(echo ${CIRCLE_TAG//v} | cut -d. -f3)
major_version_tag=$major
minor_version_tag=$major.$minor
+19
View File
@@ -363,6 +363,25 @@ module.exports = class UsersService {
return UserModel.update({id}, {$set: {status}});
}
/**
* Set the display name of a user.
* @param {String} id id of a user
* @param {String} displayName display name to set
* @param {Function} done callback after the operation is complete
*/
static setDisplayName(id, displayName) {
return UsersService.isValidDisplayName(displayName)
.then(() => { // displayName is valid
return UserModel.update(
{id},
{$set: {'displayName': displayName}})
.then(() => {
return UserModel.findOne({'id': id});
});
});
}
/**
* Finds a user with the id.
* @param {String} id user id (uuid)
+7 -2
View File
@@ -6,8 +6,8 @@ const embedStreamCommands = {
ready() {
return this
.waitForElementVisible('body', 4000)
.waitForElementVisible('iframe#coralStreamIframe')
.api.frame('coralStreamIframe');
.waitForElementVisible('#coralStreamEmbed > iframe')
.api.frame('coralStreamEmbed_iframe');
},
signUp(user) {
return this
@@ -22,6 +22,8 @@ const embedStreamCommands = {
.setValue('@signUpDialogDisplayName', user.displayName)
.waitForElementVisible('@signUpButton')
.click('@signUpButton')
.waitForElementVisible('@signInViewTrigger')
.click('@signInViewTrigger')
.waitForElementVisible('@logInButton')
.click('@logInButton')
.waitForElementVisible('@logoutButton', 5000);
@@ -102,6 +104,9 @@ module.exports = {
signUpButton: {
selector: '#coralSignUpButton'
},
signInViewTrigger: {
selector: '#coralSignInViewTrigger'
},
logoutButton: {
selector: '.commentStream #logout'
},
+10 -7
View File
@@ -4,8 +4,10 @@ const mocks = require('../mocks');
const mockComment = 'I read the comments';
const mockReply = 'This is a test reply';
const mockUser = {
email: `${new Date().getTime()}@test.com`,
name: 'testuser',
email: `${Date.now()}@test.com`,
name: `testuser${Math.random()
.toString()
.slice(-5)}`,
pw: 'testtest'
};
@@ -26,7 +28,7 @@ module.exports = {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe')
.frame('coralStreamEmbed_iframe')
// Register and Log In
.waitForElementVisible('#coralSignInButton', 2000)
@@ -39,6 +41,7 @@ module.exports = {
.setValue('#password', mockUser.pw)
.setValue('#confirmPassword', mockUser.pw)
.click('#coralSignUpButton')
.pause(5000)
.waitForElementVisible('#coralLogInButton', 10000)
.click('#coralLogInButton')
.waitForElementVisible('.coral-plugin-commentbox-button', 4000)
@@ -65,7 +68,7 @@ module.exports = {
// Load Page
client.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Post a comment
client.waitForElementVisible('.coral-plugin-commentbox-button', 2000)
@@ -91,7 +94,7 @@ module.exports = {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Post a comment
client.waitForElementVisible('.coral-plugin-commentbox-button', 2000)
@@ -138,7 +141,7 @@ module.exports = {
// Load Page
client.resizeWindow(1200, 800)
.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Post a reply
client.waitForElementVisible('.coral-plugin-replies-reply-button', 5000)
@@ -161,7 +164,7 @@ module.exports = {
'Total comment count premod on': client => {
client.perform((client, done) => {
client.url(client.globals.baseUrl)
.frame('coralStreamIframe');
.frame('coralStreamEmbed_iframe');
// Verify that comment count is correct
client.waitForElementVisible('.coral-plugin-comment-count-text', 2000)
+19
View File
@@ -178,6 +178,25 @@ describe('services.UsersService', () => {
});
});
describe('#setDisplayName', () => {
it('should set the display name to a new unique one', () => {
return UsersService
.setDisplayName(mockUsers[0].id, 'maria')
.then(() => UsersService.findById(mockUsers[0].id))
.then((user) => {
expect(user).to.have.property('displayName', 'maria');
});
});
it('should return an error when the displayName is not unique', () => {
return UsersService
.setDisplayName(mockUsers[0].id, 'marvel')
.catch((error) => {
expect(error).to.not.be.null;
});
});
});
describe('#ban', () => {
it('should set the status to banned', () => {
return UsersService
+7 -7
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1">
<title>Confirm Email</title>
<title>Email Verification</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="https://code.getmdl.io/1.2.1/material.indigo-pink.min.css">
@@ -43,14 +43,14 @@
<div id="root">
<div class="coral-card-wide mdl-card mdl-shadow--2dp">
<div class="mdl-card__title">
<h2 class="mdl-card__title-text">Confirm Email Address</h2>
<h2 class="mdl-card__title-text">Verify Email Address</h2>
</div>
<div class="mdl-card__supporting-text">
Click the button below to confirm your new user account.
Click the button below to verify the email on your new user account.
</div>
<div class="mdl-card__actions mdl-card--border">
<a class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect" id="confirm-email">
Confirm
<a class="mdl-button mdl-button--colored mdl-js-button mdl-js-ripple-effect" id="verify-email">
Verify
</a>
<div style="display: none" id="p2" class="mdl-progress mdl-js-progress mdl-progress__indeterminate"></div>
</div>
@@ -71,7 +71,7 @@
$('.error-console').removeClass('active');
$.ajax({
url: '/api/v1/account/email/confirm',
url: '/api/v1/account/email/verify',
contentType: 'application/json',
method: 'POST',
headers: {
@@ -85,7 +85,7 @@
});
}
$('#confirm-email').on('click', handleClick);
$('#verify-email').on('click', handleClick);
});
</script>
</body>
+6 -66
View File
@@ -23,72 +23,12 @@
<p><%= body %></p>
<p><a href="/admin">Admin</a> - <a href="/assets">All Assets</a></p>
<div id='coralStreamEmbed'></div>
<script src="/embed.js" async onload="
Coral.Talk.render(document.getElementById('coralStreamEmbed'), {
talk: '/',
asset: <%= asset_url ? asset_url : 'undefined' %>
})
"></script>
</main>
<script type='text/javascript' src='<%= basePath %>/pym.v1.min.js'></script>
<script>
var ready = false;
var notificationOffset = 200;
// default to using the window.location
var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
// if a url is passed into the template prefer it to the current url
<%if (asset_url.length > 0) { %>
url = '<%= asset_url %>';
<%}%>
var pymParent = new pym.Parent('coralStreamEmbed', '/embed/stream?asset_url=' + encodeURIComponent(url), {title: 'Talk Comments', id:'coralStreamIframe', name: 'coralStreamIframe', asset_url: url});
pymParent.onMessage('height', function(height) {
document.querySelector('#coralStreamEmbed iframe').height = height + 'px';
})
pymParent.onMessage('getPosition', function(notification) {
var position = viewport().height + document.body.scrollTop;
if (position > notificationOffset) {
position = position - notificationOffset;
}
pymParent.sendMessage('position', position);
});
pymParent.onMessage('childReady', function () {
var interval = setInterval(function () {
if (ready) {
window.clearInterval(interval);
// default to using the window.location
var url = window.location.hash;
// if a url is passed into the template prefer it to the current url
<%if (asset_url.length > 0) { %>
url = '<%= asset_url %>';
<%}%>
pymParent.sendMessage('DOMContentLoaded', url);
}
}, 100);
});
pymParent.onMessage('navigate', function (url) {
window.open(url, '_blank').focus();
})
// wait till images and other iframes are loaded before scrolling the page.
// or do we want to be more aggressive and scroll when we hit DOM ready?
document.addEventListener('DOMContentLoaded', function () {
ready = true;
});
function viewport() {
var e = window, a = 'inner';
if ( !( 'innerWidth' in window ) ){
a = 'client';
e = document.documentElement || document.body;
}
return { width : e[ a+'Width' ] , height : e[ a+'Height' ] }
}
</script>
</body>
</html>