Merge branch 'user-integration' of https://github.com/coralproject/talk into notification-room

This commit is contained in:
David Jay
2016-11-21 16:26:22 -05:00
14 changed files with 135 additions and 56 deletions
+42 -22
View File
@@ -5,32 +5,47 @@ import timeago from 'timeago.js';
import styles from './CommentList.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import Linkify from 'react-linkify';
const linkify = new Linkify();
// Render a single comment for the list
export default props => (
<li tabindex={props.index} className={`${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<i className={`material-icons ${styles.avatar}`}>person</i>
<span>{props.comment.get('name') || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(props.comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{props.comment.get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
export default props => {
const links = linkify.getMatches(props.comment.get('body'));
return (
<li tabindex={props.index} className={`${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<i className={`material-icons ${styles.avatar}`}>person</i>
<span>{props.comment.get('name') || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(props.comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{props.comment.get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
</div>
<div>
{links ?
<span className={styles.hasLinks}><Icon name='error_outline'/> Contains Link</span> : null}
<div className={styles.actions}>
{props.actions.map(action => canShowAction(action, props.comment) ? (
<Button className={styles.actionButton}
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
fab colored>
<Icon name={props.actionsMap[action].icon} />
</Button>
) : null)}
</div>
</div>
</div>
<div className={styles.actions}>
{props.actions.map(action => canShowAction(action, props.comment) ? (
<Button className={styles.actionButton}
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
fab colored>
<Icon name={props.actionsMap[action].icon} />
</Button>
) : null)}
<div className={styles.itemBody}>
<span className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
{props.comment.get('body')}
</Linkify>
</span>
</div>
</div>
<div className={styles.itemBody}>
<span className={styles.body}>{props.comment.get('body')}</span>
</div>
</li>
);
</li>
);
};
// Check if an action can be performed over a comment
const canShowAction = (action, comment) => {
@@ -43,4 +58,9 @@ const canShowAction = (action, comment) => {
return true;
};
const linkStyles = {
backgroundColor: 'rgb(255, 219, 135)',
padding: '1px 2px'
};
const lang = new I18n(translations);
@@ -121,3 +121,15 @@
}
}
.hasLinks {
color: #f00;
text-align: right;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
@@ -84,6 +84,7 @@ class CommentStream extends Component {
const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0];
const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
const {actions, users, comments} = this.props.items;
const {loggedIn, user, showSignInDialog} = this.props.auth;
return <div className={showSignInDialog ? 'expandForSignin' : ''}>
{
@@ -105,7 +106,7 @@ class CommentStream extends Component {
id={rootItemId}
premod={this.props.config.moderation}
reply={false}
canPost={loggedIn}
author={user}
/>
{!loggedIn && <SignInContainer />}
</div>
@@ -114,7 +115,7 @@ class CommentStream extends Component {
const comment = this.props.items.comments[commentId];
return <div className="comment" key={commentId}>
<hr aria-hidden={true}/>
<AuthorName name={comment.username}/>
<AuthorName author={users[comment.author_id]}/>
<PubDate created_at={comment.created_at}/>
<Content body={comment.body}/>
<div className="commentActionsLeft">
@@ -124,7 +125,7 @@ class CommentStream extends Component {
<LikeButton
addNotification={this.props.addNotification}
id={commentId}
like={this.props.items.actions[comment.like]}
like={actions[comment.like]}
postAction={this.props.postAction}
deleteAction={this.props.deleteAction}
addItem={this.props.addItem}
@@ -135,7 +136,7 @@ class CommentStream extends Component {
<FlagButton
addNotification={this.props.addNotification}
id={commentId}
flag={this.props.items.actions[comment.flag]}
flag={actions[comment.flag]}
postAction={this.props.postAction}
deleteAction={this.props.deleteAction}
addItem={this.props.addItem}
@@ -151,6 +152,7 @@ class CommentStream extends Component {
appendItemArray={this.props.appendItemArray}
updateItem={this.props.updateItem}
id={rootItemId}
author={user}
parent_id={commentId}
premod={this.props.config.moderation}
showReply={comment.showReply}/>
@@ -160,7 +162,7 @@ class CommentStream extends Component {
let reply = this.props.items.comments[replyId];
return <div className="reply" key={replyId}>
<hr aria-hidden={true}/>
<AuthorName name={reply.username}/>
<AuthorName author={users[comment.author_id]}/>
<PubDate created_at={reply.created_at}/>
<Content body={reply.body}/>
<div className="replyActionsLeft">
+5 -2
View File
@@ -3,6 +3,7 @@ import translations from './../translations';
const lang = new I18n(translations);
import * as actions from '../constants/auth';
import {base, handleResp, getInit} from '../helpers/response';
import {addItem} from './items'
// Dialog Actions
export const showSignInDialog = () => ({type: actions.SHOW_SIGNIN_DIALOG});
@@ -29,6 +30,7 @@ export const fetchSignIn = (formData) => dispatch => {
.then(({user}) => {
dispatch(hideSignInDialog());
dispatch(signInSuccess(user));
dispatch(addItem(user, 'users'));
})
.catch(() => dispatch(signInFailure(lang.t('error.emailPasswordError'))));
};
@@ -54,8 +56,10 @@ export const facebookCallback = (err, data) => dispatch => {
return;
}
try {
dispatch(signInFacebookSuccess(JSON.parse(data)));
const user = JSON.parse(data);
dispatch(signInFacebookSuccess(user));
dispatch(hideSignInDialog());
dispatch(addItem(user, 'users'));
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
@@ -113,4 +117,3 @@ export const logout = () => dispatch => {
export const validForm = () => ({type: actions.VALID_FORM});
export const invalidForm = error => ({type: actions.INVALID_FORM, error});
@@ -1,9 +1,9 @@
import React from 'react';
const packagename = 'coral-plugin-author-name';
const AuthorName = ({name}) =>
const AuthorName = ({author}) =>
<div className={`${packagename}-text`}>
{name}
{author && author.displayName}
</div>;
export default AuthorName;
+6 -5
View File
@@ -12,7 +12,8 @@ class CommentBox extends Component {
id: PropTypes.string,
comments: PropTypes.array,
reply: PropTypes.bool,
canPost: PropTypes.bool
canPost: PropTypes.bool,
currentUser: PropTypes.object
}
state = {
@@ -21,11 +22,11 @@ class CommentBox extends Component {
}
postComment = () => {
const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod} = this.props;
const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod, author} = this.props;
let comment = {
body: this.state.body,
asset_id: id,
username: this.state.username
author_id: author.id
};
let related;
let parent_type;
@@ -52,7 +53,7 @@ class CommentBox extends Component {
}
render () {
const {styles, reply, canPost} = this.props;
const {styles, reply, author} = this.props;
// How to handle language in plugins? Should we have a dependency on our central translation file?
return <div>
<div
@@ -73,7 +74,7 @@ class CommentBox extends Component {
rows={3}/>
</div>
<div className={`${name}-button-container`}>
{ canPost && (
{ author && (
<button
className={`${name}-button`}
style={styles && styles.button}
+6 -3
View File
@@ -4,18 +4,21 @@ import translations from './translations.json';
const name = 'coral-plugin-flags';
const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, addNotification}) => {
const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, addNotification, currentUser}) => {
const flagged = flag && flag.current_user;
const onFlagClick = () => {
if (!currentUser) {
return;
}
if (!flagged) {
postAction(id, 'flag', '123', 'comments')
postAction(id, 'flag', currentUser.id, 'comments')
.then((action) => {
addItem({...action, current_user:true}, 'actions');
updateItem(action.item_id, action.action_type, action.id, 'comments');
});
addNotification('success', lang.t('flag-notif'));
} else {
deleteAction(id, 'flag', '123', 'comments')
deleteAction(id, 'flag', currentUser.id, 'comments')
.then(() => {
updateItem(id, 'flag', '', 'comments');
});
+6 -3
View File
@@ -4,17 +4,20 @@ import translations from './translations.json';
const name = 'coral-plugin-flags';
const LikeButton = ({like, id, postAction, deleteAction, addItem, updateItem}) => {
const LikeButton = ({like, id, postAction, deleteAction, addItem, updateItem, currentUser}) => {
const liked = like && like.current_user;
const onLikeClick = () => {
if (!currentUser) {
return;
}
if (!liked) {
postAction(id, 'like', '123', 'comments')
postAction(id, 'like', currentUser.id, 'comments')
.then((action) => {
addItem({id: action.id, current_user:true, count: like ? like.count + 1 : 1}, 'actions');
updateItem(action.item_id, action.action_type, action.id, 'comments');
});
} else {
deleteAction(id, 'like', '123', 'comments')
deleteAction(id, 'like', currentUser.id, 'comments')
.then(() => {
updateItem(like.id, 'count', like.count - 1, 'actions');
updateItem(like.id, 'current_user', false, 'actions');
+2 -2
View File
@@ -106,7 +106,7 @@ input.error{
.userBox a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
margin: 0px;
}
.attention {
@@ -128,4 +128,4 @@ input.error{
.action {
margin-top: 15px;
}
}
+11 -1
View File
@@ -402,7 +402,7 @@ UserService.findById = (id) => {
};
/**
* Finds users in an array of idd.
* Finds users in an array of ids.
* @param {Array} ids array of user identifiers (uuid)
*/
UserService.findByIdArray = (ids) => {
@@ -411,6 +411,16 @@ UserService.findByIdArray = (ids) => {
});
};
/**
* Finds public user information by an array of ids.
* @param {Array} ids array of user identifiers (uuid)
*/
UserService.findPublicByIdArray = (ids) => {
return UserModel.find({
'id': {$in: ids}
}, 'id displayName');
};
/**
* Creates a JWT from a user email. Only works for local accounts.
* @param {String} email of the local user
+17 -8
View File
@@ -16,8 +16,13 @@
"config": {
"pre-git": {
"commit-msg": [],
"pre-commit": ["npm run lint", "npm test"],
"pre-push": ["npm test"],
"pre-commit": [
"npm run lint",
"npm test"
],
"pre-push": [
"npm test"
],
"post-commit": [],
"post-merge": []
}
@@ -26,7 +31,12 @@
"type": "git",
"url": "git+https://github.com/coralproject/talk.git"
},
"keywords": ["talk", "coral", "coralproject", "ask"],
"keywords": [
"talk",
"coral",
"coralproject",
"ask"
],
"author": "",
"license": "Apache-2.0",
"bugs": {
@@ -44,18 +54,17 @@
"express": "^4.14.0",
"express-session": "^1.14.2",
"helmet": "^3.1.0",
"jsonwebtoken": "^7.1.9",
"lodash": "^4.16.6",
"lodash.debounce": "^4.0.8",
"mongoose": "^4.6.5",
"morgan": "^1.7.0",
"nodemailer": "^2.6.4",
"passport": "^0.3.2",
"passport-facebook": "^2.1.1",
"passport-local": "^1.0.0",
"jsonwebtoken": "^7.1.9",
"lodash": "^4.16.6",
"mongoose": "^4.6.5",
"morgan": "^1.7.0",
"nodemailer": "^2.6.4",
"prompt": "^1.0.0",
"react-linkify": "^0.1.3",
"redis": "^2.6.3",
"uuid": "^2.0.3"
},
+1 -1
View File
@@ -35,7 +35,7 @@ router.get('/', (req, res, next) => {
return Promise.all([
[asset],
comments,
User.findByIdArray(comments.map((comment) => comment.author_id)),
User.findPublicByIdArray(comments.map((comment) => comment.author_id)),
Action.getActionSummaries(comments.map((comment) => comment.id))
]);
})
+2 -2
View File
@@ -243,9 +243,9 @@ definitions:
type: string
format: date-time
description: Display name of comment
user_id:
author_id:
type: string
description: Display name of comment
description: User who posted the comment
parent_id:
type: string
description: Display name of comment
+16
View File
@@ -43,6 +43,22 @@ describe('User: models', () => {
});
});
describe('#findPublicByIdArray()', () => {
it('should find an array of users from an array of ids', () => {
const ids = mockUsers.map((user) => user.id);
return User.findPublicByIdArray(ids).then((result) => {
expect(result).to.have.length(3);
const sorted = result.sort((a, b) => {
if(a.displayName < b.displayName) {return -1;}
if(a.displayName > b.displayName) {return 1;}
return 0;
});
expect(sorted[0]).to.have.property('displayName')
.and.to.equal('Marvel');
});
});
});
describe('#findLocalUser', () => {
it('should find a user when we give the right credentials', () => {