Merge branch 'master' into view-char-count

This commit is contained in:
David Jay
2016-12-13 14:06:38 -05:00
committed by Kim Gardner
9 changed files with 91 additions and 65 deletions
+15 -12
View File
@@ -14,18 +14,18 @@ const linkify = new Linkify();
// Render a single comment for the list
export default props => {
const authorStatus = props.author.get('status');
const {comment, author} = props;
const links = linkify.getMatches(comment.get('body'));
let authorStatus = author.status;
const links = linkify.getMatches(comment.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>{author.get('displayName') || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(comment.get('createdAt') || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{comment.get('flagged') ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
<span>{author.displayName || lang.t('comment.anon')}</span>
<span className={styles.created}>{timeago().format(comment.createdAt || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}</span>
{comment.flagged ? <p className={styles.flagged}>{lang.t('comment.flagged')}</p> : null}
</div>
<div>
{links ?
@@ -42,7 +42,7 @@ export default props => {
<div className={styles.itemBody}>
<span className={styles.body}>
<Linkify component='span' properties={{style: linkStyles}}>
{comment.get('body')}
{comment.body}
</Linkify>
</span>
</div>
@@ -52,9 +52,10 @@ export default props => {
// Get the button of the action performed over a comment if any
const getActionButton = (action, i, props) => {
const status = props.comment.get('status');
const flagged = props.comment.get('flagged');
const banned = (props.author.get('status') === 'banned');
const {comment, author} = props;
const status = comment.status;
const flagged = comment.flagged;
const banned = (author.status === 'banned');
if (action === 'flag' && (status || flagged === true)) {
return null;
@@ -64,17 +65,19 @@ const getActionButton = (action, i, props) => {
<Button
disabled={banned ? 'disabled' : ''}
cStyle='black'
onClick={() => props.onClickShowBanDialog(props.author.get('id'), props.author.get('displayName'), props.comment.get('id'))}
onClick={() => props.onClickShowBanDialog(author.id, author.displayName, comment.id)}
key={i} >
{lang.t('comment.ban_user')}
</Button>
);
}
return (
<FabButton icon={props.actionsMap[action].icon} className={styles.actionButton}
<FabButton
className={styles.actionButton}
icon={props.actionsMap[action].icon}
cStyle={action}
key={i}
onClick={() => props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))}
onClick={() => props.onClickAction(props.actionsMap[action].status, comment.id)}
/>
);
};
@@ -37,7 +37,7 @@ export default class CommentList extends React.Component {
// If entering to singleview and no active, active is the first eleement
componentWillReceiveProps (nextProps) {
if (nextProps.singleView && !this.state.active) {
this.setState({active: nextProps.commentIds.get(0)});
this.setState({active: nextProps.commentIds[0]});
}
}
@@ -81,12 +81,12 @@ export default class CommentList extends React.Component {
const {commentIds} = this.props;
const {active} = this.state;
// check boundaries
if (active === null || !commentIds.size) {
this.setState({active: commentIds.get(0)});
} else if (direction === 'up' && active !== commentIds.first()) {
this.setState({active: commentIds.get(commentIds.indexOf(active) - 1)});
} else if (direction === 'down' && active !== commentIds.last()) {
this.setState({active: commentIds.get(commentIds.indexOf(active) + 1)});
if (active === null || !commentIds.length) {
this.setState({active: commentIds[0]});
} else if (direction === 'up' && active !== commentIds[0]) {
this.setState({active: commentIds[commentIds.indexOf(active) - 1]});
} else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) {
this.setState({active: commentIds[commentIds.indexOf(active) + 1]});
}
// scroll to the position
@@ -105,10 +105,10 @@ export default class CommentList extends React.Component {
// activate the next comment
if (id === this.state.active) {
const {commentIds} = this.props;
if (commentIds.last() === this.state.active) {
this.setState({active: commentIds.get(commentIds.size - 2)});
if (commentIds[commentIds.length - 1] === this.state.active) {
this.setState({active: commentIds[commentIds.length - 2]});
} else {
this.setState({active: commentIds.get(Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.size - 1))});
this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]});
}
}
this.props.onClickAction(action, id, author_id);
@@ -125,10 +125,10 @@ export default class CommentList extends React.Component {
return (
<ul className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}>
{commentIds.map((commentId, index) => {
const comment = comments.get(commentId);
const comment = comments[commentId];
const author = users[comment.author_id];
return <Comment comment={comment}
author={users.get(comment.get('author_id'))}
ref={el => { if (el && commentId === active) { this._active = el; } }}
author={author}
key={index}
index={index}
onClickAction={this.onClickAction}
@@ -137,7 +137,7 @@ export default class CommentList extends React.Component {
actionsMap={actions}
isActive={commentId === active}
hideActive={hideActive} />;
}).toArray()}
})}
</ul>
);
}
@@ -46,9 +46,9 @@ class CommentStream extends React.Component {
<CommentBox onSubmit={this.onSubmit} />
<CommentList isActive hideActive
singleView={false}
commentIds={comments.get('ids')}
comments={comments.get('byId')}
users={users.get('byId')}
commentIds={comments.ids}
comments={comments.byId}
users={users.byId}
onClickAction={this.onClickAction}
actions={['flag']}
loading={comments.loading} />
@@ -58,4 +58,9 @@ class CommentStream extends React.Component {
}
}
export default connect(({comments, users}) => ({comments, users}))(CommentStream);
const mapStateToProps = state => ({
comments: state.comments.toJS(),
users: state.users.toJS()
});
export default connect(mapStateToProps)(CommentStream);
@@ -79,6 +79,10 @@ class ModerationQueue extends React.Component {
const {comments, users} = this.props;
const {activeTab, singleView, modalOpen} = this.state;
const premodIds = comments.ids.filter(id => comments.byId[id].status === 'premod');
const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'rejected');
const flaggedIds = comments.ids.filter(id => comments.byId[id].flagged === true);
return (
<div>
<div className='mdl-tabs mdl-js-tabs mdl-js-ripple-effect'>
@@ -94,41 +98,26 @@ class ModerationQueue extends React.Component {
<CommentList
isActive={activeTab === 'pending'}
singleView={singleView}
commentIds={
comments.get('ids')
.filter(id =>
comments
.get('byId')
.get(id)
.get('status') === 'premod')
}
comments={comments.get('byId')}
users={users.get('byId')}
commentIds={premodIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, commentId) => this.onCommentAction(action, commentId)}
onClickShowBanDialog={(userId, userName, commentId) => this.showBanUserDialog(userId, userName, commentId)}
actions={['reject', 'approve', 'ban']}
loading={comments.loading} />
<BanUserDialog
open={comments.get('showBanUserDialog')}
open={comments.showBanUserDialog}
handleClose={() => this.hideBanUserDialog()}
onClickBanUser={(userId, commentId) => this.banUser(userId, commentId)}
user={comments.get('banUser')}/>
user={comments.banUser}/>
</div>
<div className={`mdl-tabs__panel ${styles.listContainer}`} id='rejected'>
<CommentList
isActive={activeTab === 'rejected'}
singleView={singleView}
commentIds={
comments
.get('ids')
.filter(id =>
comments
.get('byId')
.get(id)
.get('status') === 'rejected')
}
comments={comments.get('byId')}
users={users.get('byId')}
commentIds={rejectedIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['approve']}
loading={comments.loading} />
@@ -137,12 +126,9 @@ class ModerationQueue extends React.Component {
<CommentList
isActive={activeTab === 'rejected'}
singleView={singleView}
commentIds={comments.get('ids').filter(id => {
const data = comments.get('byId').get(id);
return !data.get('status') && data.get('flagged') === true;
})}
comments={comments.get('byId')}
users={users.get('byId')}
commentIds={flaggedIds}
comments={comments.byId}
users={users.byId}
onClickAction={(action, id) => this.onCommentAction(action, id)}
actions={['reject', 'approve']}
loading={comments.loading} />
@@ -155,6 +141,11 @@ class ModerationQueue extends React.Component {
}
}
export default connect(({comments, users}) => ({comments, users}))(ModerationQueue);
const mapStateToProps = state => ({
comments: state.comments.toJS(),
users: state.users.toJS()
});
export default connect(mapStateToProps)(ModerationQueue);
const lang = new I18n(translations);
+1 -1
View File
@@ -101,7 +101,7 @@
},
"configure": {
"enable-pre-moderation": "Habilitar pre-moderación",
"enable-pre-moderation-text": "tracundeme!",
"enable-pre-moderation-text": "Los moderadores deben aprobar cada comentario antes de que sea publicado.",
"include-comment-stream": "Incluir la Descripción a un Hilo de Comentario para los y las Lectoras.",
"include-comment-stream-desc": "Escribir un mensaje que será agregado a la parte de arriba del tu hilo de comentarios. Por ejemplo, un tema, guias de comunidad, etc.",
"include-text": "Incluir tu texto aqui.",
+1 -1
View File
@@ -74,7 +74,7 @@ const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error});
export const fetchSignUp = formData => dispatch => {
dispatch(signUpRequest());
coralApi('/user', {method: 'POST', body: formData})
coralApi('/users', {method: 'POST', body: formData})
.then(({user}) => {
dispatch(signUpSuccess(user));
setTimeout(() =>{
+1 -2
View File
@@ -60,8 +60,7 @@ class CommentBox extends Component {
postItem(comment, 'comments')
.then((postedComment) => {
const commentId = postedComment.id;
const status = postedComment.status;
if (status[0] && status[0].type === 'rejected') {
if (postedComment.status === 'rejected') {
addNotification('error', lang.t('comment-post-banned-word'));
} else if (premod === 'pre') {
addNotification('success', lang.t('comment-post-notif-premod'));
+7 -1
View File
@@ -97,7 +97,13 @@ router.post('/', wordlist.filter('body'), (req, res, next) => {
// Return `premod` if pre-moderation is enabled and an empty "new" status
// in the event that it is not in pre-moderation mode.
.then(({moderation}) => moderation === 'pre' ? 'premod' : '');
.then(({moderation, charCountEnable, charCount}) => {
// Reject if the comment is too long
if (charCountEnable && body.length > charCount) {
return 'rejected';
}
return moderation === 'pre' ? 'premod' : '';
});
}
status.then((status) => Comment.publicCreate({
+22
View File
@@ -197,6 +197,28 @@ describe('/api/v1/comments', () => {
});
});
it('should create a rejected comment if the body is above the character count', () => {
return Asset
.findOrCreateByUrl('https://coralproject.net/article1')
.then((asset) => {
return Asset
.overrideSettings(asset.id, {charCountEnable: true, charCount: 10})
.then(() => asset);
})
.then((asset) => {
return chai.request(app)
.post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'This is way way way way way too long.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''});
})
.then((res) => {
expect(res).to.have.status(201);
expect(res.body).to.have.property('id');
expect(res.body).to.have.property('asset_id');
expect(res.body).to.have.property('status', 'rejected');
});
});
it('shouldn\'t create a comment when the asset has expired commenting', () => {
return Asset.create({
closedAt: new Date().setDate(0),