Merge branch 'master' into story-138187767-mod-flag-names

This commit is contained in:
gaba
2017-02-27 07:46:23 -08:00
22 changed files with 204 additions and 233 deletions
+1 -1
View File
@@ -29,5 +29,5 @@
"as": "POSTMARK"
}],
"image": "heroku/nodejs",
"success_url": "/admin/setup"
"success_url": "/admin/install"
}
@@ -52,7 +52,6 @@ const updateClosedMessage = (updateSettings) => (event) => {
};
const updateCustomCssUrl = (updateSettings) => (event) => {
console.log('updateCustomCssUrl', event.target.value);
const customCssUrl = event.target.value;
updateSettings({customCssUrl});
};
+6 -3
View File
@@ -2,6 +2,9 @@ import React, {Component} from 'react';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import isEqual from 'lodash/isEqual';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-framework/translations';
const lang = new I18n(translations);
import {TabBar, Tab, TabContent, Spinner} from 'coral-ui';
@@ -25,7 +28,7 @@ import UserBox from 'coral-sign-in/components/UserBox';
import SignInContainer from 'coral-sign-in/containers/SignInContainer';
import SuspendedAccount from 'coral-framework/components/SuspendedAccount';
import ChangeUsernameContainer from '../../coral-sign-in/containers/ChangeUsernameContainer';
import SettingsContainer from 'coral-settings/containers/SettingsContainer';
import ProfileContainer from 'coral-settings/containers/ProfileContainer';
import RestrictedContent from 'coral-framework/components/RestrictedContent';
import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer';
import LoadMore from './LoadMore';
@@ -110,7 +113,7 @@ class Embed extends Component {
<div className="commentStream">
<TabBar onChange={this.changeTab} activeTab={activeTab}>
<Tab><Count count={asset.commentCount}/></Tab>
<Tab>Settings</Tab>
<Tab>{lang.t('profile')}</Tab>
<Tab restricted={!isAdmin}>Configure Stream</Tab>
</TabBar>
{loggedIn && <UserBox user={user} logout={this.props.logout} changeTab={this.changeTab}/>}
@@ -190,7 +193,7 @@ class Embed extends Component {
loadMore={this.props.loadMore}/>
</TabContent>
<TabContent show={activeTab === 1}>
<SettingsContainer
<ProfileContainer
loggedIn={loggedIn}
userData={this.props.userData}
showSignInDialog={this.props.showSignInDialog}
+1 -2
View File
@@ -192,9 +192,8 @@ export const requestConfirmEmail = (email, redirectUri) => dispatch => {
dispatch(verifyEmailSuccess());
})
.catch(err => {
console.log('failed to send email verification', err);
// email might have already been verifyed
dispatch(verifyEmailFailure());
dispatch(verifyEmailFailure(err));
});
};
+1 -1
View File
@@ -45,7 +45,7 @@ const handleResp = res => {
}
if (err.error && err.error.translation_key) {
message = err.error.translation_key;
error.translation_key = err.error.translation_key;
}
error.message = message;
+2
View File
@@ -1,5 +1,6 @@
{
"en": {
"profile": "Profile",
"successUpdateSettings": "The changes you have made have been applied to the comment stream on this article",
"successNameUpdate": "Your username has been updated",
"contentNotAvailable": "This content is not available",
@@ -36,6 +37,7 @@
}
},
"es": {
"profile": "Perfil",
"successUpdateSettings": "La configuración de este articulo fue actualizada",
"successBioUpdate": "Tu bio fue actualizada",
"contentNotAvailable": "El contenido no se encuentra disponible",
+4 -6
View File
@@ -30,6 +30,7 @@ class CommentBox extends Component {
postItem,
assetId,
updateCountCache,
isReply,
countCache,
parentId,
addNotification,
@@ -46,19 +47,17 @@ class CommentBox extends Component {
if (this.props.charCount && this.state.body.length > this.props.charCount) {
return;
}
updateCountCache(assetId, countCache + 1);
!isReply && updateCountCache(assetId, countCache + 1);
postItem(comment, 'comments')
.then(({data}) => {
const postedComment = data.createComment.comment;
if (postedComment.status === 'REJECTED') {
addNotification('error', lang.t('comment-post-banned-word'));
updateCountCache(assetId, countCache);
!isReply && updateCountCache(assetId, countCache);
} else if (postedComment.status === 'PREMOD') {
addNotification('success', lang.t('comment-post-notif-premod'));
updateCountCache(assetId, countCache);
} else {
addNotification('success', 'Your comment has been posted.');
!isReply && updateCountCache(assetId, countCache);
}
if (commentPostedHandler) {
@@ -110,7 +109,6 @@ class CommentBox extends Component {
cStyle='darkGrey'
className={`${name}-cancel-button`}
onClick={() => {
console.log('cancel button in comment box');
cancelButtonClicked('');
}}>
{lang.t('cancel')}
@@ -1,58 +0,0 @@
import React, {Component} from 'react';
import {graphql} from 'react-apollo';
import gql from 'graphql-tag';
export class RileysAwesomeCommentBox extends Component {
postComment() {
console.log(this.props);
console.log('postComment', this.props.asset_id);
this.props.mutate({
variables: {
asset_id: this.props.asset_id,
body: this.textarea.value,
parent_id: null
}
}).then(({data}) => {
console.log('it workt');
console.log(data);
});
}
render() {
return <div>
<textarea ref={textarea => this.textarea = textarea}></textarea>
<button onClick={this.postComment.bind(this)}>POST</button>
</div>;
}
}
const postComment = gql`
fragment commentView on Comment {
id
body
user {
name: username
}
actions {
type: action_type
count
current: current_user {
id
created_at
}
}
}
mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) {
createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) {
...commentView
}
}
`;
const RileysAwesomeCommentBoxWithData = graphql(
postComment
)(RileysAwesomeCommentBox);
export default RileysAwesomeCommentBoxWithData;
-100
View File
@@ -1,100 +0,0 @@
import React, {Component} from 'react';
import {graphql} from 'react-apollo';
import gql from 'graphql-tag';
import {fetchSignIn} from 'coral-framework/actions/auth';
import RileysAwesomeCommentBox from 'coral-plugin-stream/RileysAwesomeCommentBox';
const assetID = '6187a94b-0b6d-4a96-ac6b-62b529cd8410';
// MyComponent is a "presentational" or apollo-unaware component,
// It could be a simple React class:
class Stream extends Component {
constructor(props) {
super(props);
}
logMeIn() {
fetchSignIn({email: 'your@example.com', password: 'dfasidfaisdufoiausdfoiuaspdoifas'})(() => {});
}
render() {
const {data} = this.props;
return <div>
<button onClick={this.logMeIn.bind(this)}>Login or whatever</button>
{
data.loading
? 'loading!'
: <div>
<RileysAwesomeCommentBox asset_id={data.asset.id} />
<p>Asset ID: {data.asset.id}</p>
<ul>
{
data.asset.comments.map(comment => {
return <li key={comment.id}>
{comment.body} [{comment.id}]
<ul>
{
comment.replies.map(reply => {
return <li key={reply.id}>{reply.body}</li>;
})
}
</ul>
</li>;
})
}
</ul>
</div>
}
</div>;
}
}
// Initialize GraphQL queries or mutations with the gql tag
const StreamQuery = gql`fragment commentView on Comment {
id
body
user {
name: username
}
tags {
name
}
actions {
type: action_type
count
current: current_user {
id
created_at
}
}
}
query AssetQuery($asset_id: ID!) {
asset(id: $asset_id) {
id
title
url
commentCount
comments {
...commentView
replies {
...commentView
}
}
}
}`;
// We then can use `graphql` to pass the query results returned by MyQuery
// to MyComponent as a prop (and update them as the results change)
const StreamWithData = graphql(
StreamQuery, {
options: {
variables: {
asset_id: assetID
}
}
}
)(Stream);
export default StreamWithData;
@@ -10,7 +10,6 @@ export default ({showSignInDialog}) => (
<SignInContainer noButton={true}/>
<div>
<a onClick={() => {
console.log('Signin click');
showSignInDialog();
}}>{lang.t('signIn')}</a> {lang.t('toAccess')}
</div>
@@ -0,0 +1,12 @@
import React, {PropTypes} from 'react';
import styles from './ProfileHeader.css';
const ProfileHeader = ({username}) => (
<div className={styles.header}>
<h1>{username}</h1>
</div>
);
ProfileHeader.propTypes = {username: PropTypes.string.isRequired};
export default ProfileHeader;
@@ -1,14 +0,0 @@
import React from 'react';
import styles from './SettingsHeader.css';
export default ({userData}) => (
<div className={styles.header}>
<h1>{userData.username}</h1>
{
// Hiding display of users ID unless there's a use case for it.
// <h2>{userData.profiles.map(profile => profile.id)}</h2>
}
</div>
);
@@ -8,13 +8,13 @@ import {myCommentHistory} from 'coral-framework/graphql/queries';
import {link} from 'coral-framework/services/PymConnection';
import NotLoggedIn from '../components/NotLoggedIn';
import {Spinner} from 'coral-ui';
import SettingsHeader from '../components/SettingsHeader';
import ProfileHeader from '../components/ProfileHeader';
import CommentHistory from 'coral-plugin-history/CommentHistory';
import translations from '../translations';
const lang = new I18n(translations);
class SettingsContainer extends Component {
class ProfileContainer extends Component {
constructor (props) {
super(props);
this.state = {
@@ -44,7 +44,7 @@ class SettingsContainer extends Component {
return (
<div>
<SettingsHeader {...this.props} />
<ProfileHeader username={this.props.userData.username} />
{
// Hiding bio until moderation can get figured out
@@ -88,4 +88,4 @@ const mapDispatchToProps = () => ({
export default compose(
connect(mapStateToProps, mapDispatchToProps),
myCommentHistory
)(SettingsContainer);
)(ProfileContainer);
+5 -3
View File
@@ -1,20 +1,22 @@
{
"en":{
"profile": "Profile",
"userNoComment": "You've never left a comment. Join the conversation!",
"allComments": "All Comments",
"profileSettings": "Profile Settings",
"myCommentHistory": "My comment History",
"signIn": "Sign in",
"toAccess": " to access Settings",
"fromSettingsPage": "From the Settings Page you can see your comment history."
"toAccess": " to access Profile",
"fromSettingsPage": "From the Profile Page you can see your comment history."
},
"es":{
"profile": "Perfil",
"userNoComment": "No has dejado áun ningún comentario. ¡Unete a la conversación!",
"allComments": "Todos los comentarios",
"profileSettings": "Configuración del perfil",
"myCommentHistory": "Mi historial de comentarios",
"signIn": "Entrar",
"toAccess": "para acceder a la configuración",
"toAccess": "para acceder a al perfil",
"fromSettingsPage": "Desde la peagina de configuración puede ver su historia de comentarios."
}
}
@@ -3,14 +3,18 @@ import TextField from 'coral-ui/components/TextField';
import Alert from './Alert';
import Button from 'coral-ui/components/Button';
import {Dialog} from 'coral-ui';
import FakeComment from './FakeComment';
import styles from './styles.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
const CreateUsernameDialog = ({open, handleClose, offset, formData, handleSubmitUsername, handleChange, ...props}) => (
const CreateUsernameDialog = ({open, handleClose, offset, formData, handleSubmitUsername, handleChange, ...props}) => {
return (
<Dialog
className={styles.dialog}
className={styles.dialogusername}
id="createUsernameDialog"
open={open}
style={{
@@ -25,24 +29,32 @@ const CreateUsernameDialog = ({open, handleClose, offset, formData, handleSubmit
</h1>
</div>
<div>
<label htmlFor="username">{lang.t('createdisplay.yourusername')}</label>
<p className={styles.yourusername}>{lang.t('createdisplay.yourusername')}</p>
<FakeComment
className={styles.fakeComment}
username={formData.username}
created_at={Date.now()}
body={lang.t('createdisplay.fakecommentbody')}
/>
<p className={styles.ifyoudont}>{lang.t('createdisplay.ifyoudontchangeyourname')}</p>
{ props.auth.error && <Alert>{props.auth.error}</Alert> }
<form id="saveUsername" onSubmit={handleSubmitUsername}>
<TextField
id="username"
type="string"
label={lang.t('createdisplay.username')}
value={formData.username}
onChange={handleChange}
/>
{ props.errors.username && <span className={styles.hint}> {lang.t('createdisplay.specialCharacters')} </span> }
<div className={styles.action}>
{ props.errors.username && <span className={styles.hint}> {lang.t('createdisplay.specialCharacters')} </span> }
<div className={styles.saveusername}>
<TextField
id="username"
type="string"
label={lang.t('createdisplay.username')}
value={formData.username}
onChange={handleChange}
/>
<Button id="save" type="submit" className={styles.saveButton}>{lang.t('createdisplay.save')}</Button>
</div>
</form>
</div>
</div>
</Dialog>
);
);
};
export default CreateUsernameDialog;
@@ -0,0 +1,66 @@
import React from 'react';
import styles from 'coral-embed-stream/src/Comment.css';
import AuthorName from 'coral-plugin-author-name/AuthorName';
import Content from 'coral-plugin-commentcontent/CommentContent';
import PubDate from 'coral-plugin-pubdate/PubDate';
import {ReplyButton} from 'coral-plugin-replies';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
const lang = new I18n(translations);
class FakeComment extends React.Component {
constructor (props) {
super(props);
}
render () {
const {username, created_at, body} = this.props;
return (
<div
className={`comment ${styles.Comment}`}
style={{marginLeft: 0 * 30}}>
<hr aria-hidden={true} />
<AuthorName
author={{'name': username}}/>
<PubDate created_at={created_at} />
<Content body={body} />
<div className="commentActionsLeft">
<div className={`${'coral-plugin-likes'}-container`}>
<button className={`${'coral-plugin-likes'}-button`}>
<span className={`${'coral-plugin-likes'}-button-text`}>{lang.t('like')}</span>
<i className={`${'coral-plugin-likes'}-icon material-icons`}
aria-hidden={true}>thumb_up</i>
</button>
</div>
<ReplyButton
onClick={() => {}}
parentCommentId={'commentID'}
currentUserId={{}}
banned={false}
/>
</div>
<div className="commentActionsRight">
<div className="coral-plugin-permalinks-container">
<button className="coral-plugin-permalinks-button">
<i className="coral-plugin-permalinks-icon material-icons" aria-hidden={true}>link</i>
{lang.t('permalink.permalink')}
</button>
</div>
<div className={`${'coral-plugin-flags'}-container`}>
<button className={`${'coral-plugin-flags'}-button`}>
<span className={`${'coral-plugin-flags'}-button-text`}>{lang.t('report')}</span>
<i className={`${'coral-plugin-flags'}-icon material-icons`}
aria-hidden={true}>flag</i>
</button>
</div>
</div>
</div>
);
}
}
export default FakeComment;
+36 -2
View File
@@ -118,7 +118,7 @@ input.error{
}
.action {
margin-top: 15px;
margin-top: 0px;
}
.passwordRequestSuccess {
@@ -141,6 +141,40 @@ input.error{
display: block;
}
.confirmSubmit {
/* Change username Dialog*/
.dialogusername {
border: none;
box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2);
width: 400px;
top: 10px;
}
.yourusername {
display: block;
}
.example {
display: block;
}
.ifyoudont {
display: block;
margin-top: 15px;
}
.saveusername {
display: block;
width: 100%;
}
.savebutton {
display: inline;
background-color: rgb(105,105,105);
color: white;
}
.fakeComment {
display: block;
margin-bottom: 5px;
}
@@ -29,6 +29,7 @@ class ChangeUsernameContainer extends Component {
constructor(props) {
super(props);
this.initialState.formData.username = props.user.username;
this.state = this.initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmitUsername = this.handleSubmitUsername.bind(this);
@@ -103,7 +104,7 @@ class ChangeUsernameContainer extends Component {
return (
<div>
<CreateUsernameDialog
open={auth.showCreateUsernameDialog && auth.fromSignUp}
open={auth.showCreateUsernameDialog && auth.user.canEditName}
offset={offset}
handleClose={this.handleClose}
loggedIn={loggedIn}
+26 -8
View File
@@ -10,7 +10,7 @@ export default {
facebookSignIn: 'Sign in with Facebook',
facebookSignUp: 'Sign up with Facebook',
logout: 'Logout',
signIn: 'Sign In',
signIn: 'Sign in to join the conversation',
or: 'Or',
email: 'E-mail Address',
password: 'Password',
@@ -30,15 +30,24 @@ export default {
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.',
writeyourusername: 'Edit your username',
yourusername: 'Your username appears on every comment you post.',
ifyoudontchangeyourname: 'If you don\'t change your username at this step, your Facebook display name will appear alongside of all your comments.',
username: 'Username',
continue: 'Continue with the same Facebook username',
save: 'Save',
fakecommentdate: '1 minute ago',
fakecommentbody: 'This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section.',
requiredField: 'Required field',
errorCreate: 'Error when changing username',
checkTheForm: 'Invalid Form. Please, check the fields',
specialCharacters: 'Usernames can contain letters, numbers and _ only'
}
},
'permalink': {
permalink: 'Link'
},
'report': 'Report',
'like': 'Like',
},
es: {
'signIn': {
@@ -51,7 +60,7 @@ export default {
facebookSignIn: 'Entrar con Facebook',
facebookSignUp: 'Regístrate con Facebook',
logout: 'Salir',
signIn: 'Entrar',
signIn: 'Entrar para Unirte a la Conversación',
or: 'o',
email: 'E-mail',
password: 'Contraseña',
@@ -71,14 +80,23 @@ export default {
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.',
username: 'Nombre a mostrar',
writeyourusername: 'Edita tu nombre',
yourusername: 'Tu nombre aparece en cada comentario que publiques.',
ifyoudontchangeyourname: 'Si no modificas tu nombre de usuario en este paso, tu nombre de Facebook aparecera al lado de cada comentario que publiques.',
username: 'Nombre',
continue: 'Continuar con nombre de Facebook',
save: 'Guardar',
fakecommentdate: 'hace un minuto',
fakecommentbody: 'Este es un comentario de ejemplo. Las lectoras pueden compartir sus ideas y opiniones con los periodistas en la sección de comentarios.',
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 _'
},
'permalink': {
permalink: 'Enlace'
},
'report': 'Informe',
'like': 'Me gusta',
}
};
+9 -14
View File
@@ -2,7 +2,6 @@ const express = require('express');
const passport = require('../../../services/passport');
const authorization = require('../../../middleware/authorization');
const errors = require('../../../errors');
const UsersService = require('../../../services/users');
const router = express.Router();
@@ -61,6 +60,7 @@ const HandleAuthCallback = (req, res, next) => (err, user) => {
/**
* Returns the response to the login attempt via a popup callback with some JS.
*/
const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
if (err) {
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
@@ -70,20 +70,15 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => {
return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null});
}
// Authorize the user to edit their username.
UsersService.toggleNameEdit(user.id, true)
.then(() => {
// Perform the login of the user!
req.logIn(user, (err) => {
if (err) {
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
}
// Perform the login of the user!
req.logIn(user, (err) => {
if (err) {
return res.render('auth-callback', {err: JSON.stringify(err), data: null});
}
// We logged in the user! Let's send back the user data.
res.render('auth-callback', {err: null, data: JSON.stringify(user)});
});
});
// We logged in the user! Let's send back the user data.
res.render('auth-callback', {err: null, data: JSON.stringify(user)});
});
};
/**
+4 -1
View File
@@ -124,6 +124,8 @@ module.exports = class UsersService {
return user;
}
// User does not exist and need to be created.
let username = UsersService.castUsername(displayName);
// The user was not found, lets create them!
@@ -131,7 +133,8 @@ module.exports = class UsersService {
username,
lowercaseUsername: username.toLowerCase(),
roles: [],
profiles: [{id, provider}]
profiles: [{id, provider}],
canEditName: true
});
return user.save();