mirror of
https://github.com/wassname/talk.git
synced 2026-08-02 13:10:23 +08:00
Merge branch 'user-integration' of https://github.com/coralproject/talk into notification-room
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user