mirror of
https://github.com/wassname/talk.git
synced 2026-07-01 16:20:03 +08:00
Merge branch 'master' into use-js-while-rendering
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
"comment-settings": "Comment Settings",
|
||||
"embed-comment-stream": "Embed Comment Stream",
|
||||
"banned-word-header": "Write the bannned words list",
|
||||
"banned-word-text": "Comments which contain these words or phrases, not seperated by commas and not case sensitive, will be automatically removed from the comment stream.",
|
||||
"banned-word-text": "Comments which contain these words or phrases, not separated by commas and not case sensitive, will be automatically removed from the comment stream.",
|
||||
"wordlist": "Banned words list",
|
||||
"save-changes": "Save Changes",
|
||||
"copy-and-paste": "Copy and paste code below into your CMS to embed your comment box in your articles",
|
||||
|
||||
@@ -16,7 +16,7 @@ import PubDate from '../../coral-plugin-pubdate/PubDate';
|
||||
import Count from '../../coral-plugin-comment-count/CommentCount';
|
||||
import AuthorName from '../../coral-plugin-author-name/AuthorName';
|
||||
import {ReplyBox, ReplyButton} from '../../coral-plugin-replies';
|
||||
import FlagButton from '../../coral-plugin-flags/FlagButton';
|
||||
import FlagComment from '../../coral-plugin-flags/FlagComment';
|
||||
import LikeButton from '../../coral-plugin-likes/LikeButton';
|
||||
import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton';
|
||||
import SignInContainer from '../../coral-sign-in/containers/SignInContainer';
|
||||
@@ -90,8 +90,8 @@ 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 {status, moderation, closedMessage} = this.props.config;
|
||||
const {loggedIn, user, showSignInDialog, signInOffset} = this.props.auth;
|
||||
const {status, closedMessage} = this.props.config;
|
||||
const {activeTab} = this.state;
|
||||
const banned = (this.props.userData.status === 'banned');
|
||||
|
||||
@@ -123,7 +123,7 @@ class CommentStream extends Component {
|
||||
appendItemArray={this.props.appendItemArray}
|
||||
updateItem={this.props.updateItem}
|
||||
id={rootItemId}
|
||||
premod={this.props.config.moderation}
|
||||
premod={moderation}
|
||||
reply={false}
|
||||
currentUser={this.props.auth.user}
|
||||
banned={banned}
|
||||
@@ -139,7 +139,17 @@ class CommentStream extends Component {
|
||||
const comment = comments[commentId];
|
||||
return <div className="comment" key={commentId} id={`c_${commentId}`}>
|
||||
<hr aria-hidden={true}/>
|
||||
<AuthorName author={users[comment.author_id]}/>
|
||||
<AuthorName
|
||||
author={users[comment.author_id]}
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
author_id={comment.author_id}
|
||||
postAction={this.props.postAction}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
deleteAction={this.props.deleteAction}
|
||||
addItem={this.props.addItem}
|
||||
updateItem={this.props.updateItem}
|
||||
currentUser={this.props.auth.user}/>
|
||||
<PubDate created_at={comment.created_at}/>
|
||||
<Content body={comment.body}/>
|
||||
<div className="commentActionsLeft">
|
||||
@@ -162,9 +172,10 @@ class CommentStream extends Component {
|
||||
banned={banned}/>
|
||||
</div>
|
||||
<div className="commentActionsRight">
|
||||
<FlagButton
|
||||
<FlagComment
|
||||
addNotification={this.props.addNotification}
|
||||
id={commentId}
|
||||
author_id={comment.author_id}
|
||||
flag={actions[comment.flag]}
|
||||
postAction={this.props.postAction}
|
||||
deleteAction={this.props.deleteAction}
|
||||
@@ -185,8 +196,8 @@ class CommentStream extends Component {
|
||||
id={rootItemId}
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
currentUser={this.props.auth.user}
|
||||
premod={this.props.config.moderation}
|
||||
premod={moderation}
|
||||
currentUser={user}
|
||||
showReply={comment.showReply}/>
|
||||
{
|
||||
comment.children &&
|
||||
@@ -217,10 +228,11 @@ class CommentStream extends Component {
|
||||
banned={banned}/>
|
||||
</div>
|
||||
<div className="replyActionsRight">
|
||||
<FlagButton
|
||||
<FlagComment
|
||||
addNotification={this.props.addNotification}
|
||||
id={replyId}
|
||||
flag={this.props.items.actions[reply.flag]}
|
||||
author_id={comment.author_id}
|
||||
flag={actions[reply.flag]}
|
||||
postAction={this.props.postAction}
|
||||
showSignInDialog={this.props.showSignInDialog}
|
||||
deleteAction={this.props.deleteAction}
|
||||
@@ -242,9 +254,9 @@ class CommentStream extends Component {
|
||||
author={user}
|
||||
parent_id={commentId}
|
||||
child_id={replyId}
|
||||
premod={this.props.config.moderation}
|
||||
premod={moderation}
|
||||
banned={banned}
|
||||
currentUser={this.props.auth.user}
|
||||
currentUser={user}
|
||||
showReply={reply.showReply}/>
|
||||
</div>;
|
||||
})
|
||||
@@ -295,14 +307,14 @@ const mapStateToProps = state => ({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
addItem: (item, itemType) => dispatch(addItem(item, itemType)),
|
||||
addItem: (item, item_id) => dispatch(addItem(item, item_id)),
|
||||
updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)),
|
||||
postItem: (data, type, id) => dispatch(postItem(data, type, id)),
|
||||
getStream: (rootId) => dispatch(getStream(rootId)),
|
||||
addNotification: (type, text) => dispatch(addNotification(type, text)),
|
||||
clearNotification: () => dispatch(clearNotification()),
|
||||
postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)),
|
||||
showSignInDialog: (offset) => dispatch(showSignInDialog(offset)),
|
||||
postAction: (item, action, user, itemType) => dispatch(postAction(item, action, user, itemType)),
|
||||
deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)),
|
||||
appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)),
|
||||
handleSignInDialog: () => dispatch(authActions.showSignInDialog()),
|
||||
|
||||
@@ -127,6 +127,10 @@ hr {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.coral-plugin-author-name-bio-flag {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
/* Reply styles */
|
||||
|
||||
@@ -197,6 +201,54 @@ hr {
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
/* Flag Styles */
|
||||
|
||||
.coral-plugin-flags-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup span {
|
||||
min-width: 280px;
|
||||
bottom: 36px;
|
||||
left: -190px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup-form {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup-header {
|
||||
font-weight: bolder;
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup-radio {
|
||||
margin:5px;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup-radio-label {
|
||||
margin:5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup-counter {
|
||||
float: left;
|
||||
margin-top: 21px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-popup-button {
|
||||
float: right;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.coral-plugin-flags-other-text {
|
||||
margin-left: 20px;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
/* Close comments */
|
||||
|
||||
.close-comments-intro-wrapper {
|
||||
|
||||
@@ -212,13 +212,8 @@ export function postItem (item, type, id) {
|
||||
*
|
||||
*/
|
||||
|
||||
export function postAction (item_id, action_type, user_id, item_type) {
|
||||
export function postAction (item_id, item_type, action) {
|
||||
return () => {
|
||||
const action = {
|
||||
action_type,
|
||||
user_id
|
||||
};
|
||||
|
||||
return coralApi(`/${item_type}/${item_id}/actions`, {method: 'POST', body: action});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Tooltip} from 'coral-ui';
|
||||
import FlagBio from '../coral-plugin-flags/FlagBio';
|
||||
const packagename = 'coral-plugin-author-name';
|
||||
|
||||
export default class AuthorName extends Component {
|
||||
@@ -36,7 +37,15 @@ export default class AuthorName extends Component {
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
{author && author.displayName}
|
||||
{ showTooltip && <Tooltip>{author.settings.bio}</Tooltip>}
|
||||
{ showTooltip && <Tooltip>
|
||||
<div className={`${packagename}-bio`}>
|
||||
{author.settings.bio}
|
||||
</div>
|
||||
<div className={`${packagename}-bio-flag`}>
|
||||
<FlagBio {...this.props}/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import FlagButton from './FlagButton';
|
||||
import {I18n} from '../coral-framework';
|
||||
import translations from './translations.json';
|
||||
|
||||
const FlagBio = (props) => <FlagButton {...props} getPopupMenu={getPopupMenu} />;
|
||||
|
||||
const getPopupMenu = [
|
||||
() => {
|
||||
return {
|
||||
header: lang.t('step-2-header'),
|
||||
itemType: 'user',
|
||||
field: 'bio',
|
||||
options: [
|
||||
{val: 'This bio is offensive', text: lang.t('bio-offensive')},
|
||||
{val: 'I don\'t like this bio', text: lang.t('no-like-bio')},
|
||||
{val: 'This looks like an ad/marketing', text: lang.t('marketing')},
|
||||
{val: 'other', text: lang.t('other')}
|
||||
],
|
||||
button: lang.t('continue'),
|
||||
sets: 'detail'
|
||||
};
|
||||
},
|
||||
() => {
|
||||
return {
|
||||
header: lang.t('step-3-header'),
|
||||
text: lang.t('thank-you'),
|
||||
button: lang.t('done'),
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
export default FlagBio;
|
||||
|
||||
const lang = new I18n(translations);
|
||||
@@ -1,52 +1,178 @@
|
||||
import React from 'react';
|
||||
import React, {Component} from 'react';
|
||||
import {I18n} from '../coral-framework';
|
||||
import translations from './translations.json';
|
||||
import {PopupMenu, Button} from 'coral-ui';
|
||||
import onClickOutside from 'react-onclickoutside';
|
||||
|
||||
const name = 'coral-plugin-flags';
|
||||
|
||||
const FlagButton = ({flag, id, postAction, deleteAction, addItem, showSignInDialog, updateItem, addNotification, currentUser, banned}) => {
|
||||
const flagged = flag && flag.current_user;
|
||||
const onFlagClick = () => {
|
||||
if (!currentUser) {
|
||||
const offset = document.getElementById(`c_${id}`).getBoundingClientRect().top - 75;
|
||||
showSignInDialog(offset);
|
||||
class FlagButton extends Component {
|
||||
|
||||
state = {
|
||||
showMenu: false,
|
||||
showOther: false,
|
||||
itemType: '',
|
||||
detail: '',
|
||||
otherText: '',
|
||||
step: 0,
|
||||
posted: false
|
||||
}
|
||||
|
||||
// When the "report" button is clicked expand the menu
|
||||
onReportClick = () => {
|
||||
if (!this.props.currentUser) {
|
||||
const offset = document.getElementById(`c_${this.props.id}`).getBoundingClientRect().top - 75;
|
||||
this.props.showSignInDialog(offset);
|
||||
return;
|
||||
}
|
||||
if (banned) {
|
||||
return;
|
||||
this.setState({showMenu: !this.state.showMenu});
|
||||
}
|
||||
|
||||
onPopupContinue = () => {
|
||||
const {postAction, addItem, updateItem, flag, id, author_id} = this.props;
|
||||
const {itemType, field, detail, step, otherText, posted} = this.state;
|
||||
|
||||
//Proceed to the next step or close the menu if we've reached the end
|
||||
if (step + 1 >= this.props.getPopupMenu.length) {
|
||||
this.setState({showMenu: false});
|
||||
} else {
|
||||
this.setState({step: step + 1});
|
||||
}
|
||||
if (!flagged) {
|
||||
postAction(id, 'flag', currentUser.id, 'comments')
|
||||
|
||||
// If itemType and detail are both set, post the action
|
||||
if (itemType && detail && !posted) {
|
||||
// Set the text from the "other" field if it exists.
|
||||
const updatedDetail = otherText || detail;
|
||||
let item_id;
|
||||
switch(itemType) {
|
||||
case 'comments':
|
||||
item_id = id;
|
||||
break;
|
||||
case 'user':
|
||||
item_id = author_id;
|
||||
break;
|
||||
}
|
||||
const action = {
|
||||
action_type: 'flag',
|
||||
field,
|
||||
detail: updatedDetail
|
||||
};
|
||||
postAction(item_id, itemType, action)
|
||||
.then((action) => {
|
||||
let id = `${action.action_type}_${action.item_id}`;
|
||||
addItem({id, current_user: action, count: flag ? flag.count + 1 : 1}, 'actions');
|
||||
updateItem(action.item_id, action.action_type, id, 'comments');
|
||||
updateItem(action.item_id, action.action_type, id, action.item_type);
|
||||
this.setState({posted: true});
|
||||
});
|
||||
addNotification('success', lang.t('flag-notif'));
|
||||
} else {
|
||||
deleteAction(flagged.id)
|
||||
.then(() => {
|
||||
updateItem(id, 'flag', '', 'comments');
|
||||
});
|
||||
addNotification('success', lang.t('flag-notif-remove'));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return <div className={`${name}-container`}>
|
||||
<button onClick={onFlagClick} className={`${name}-button`}>
|
||||
onPopupOptionClick = (sets) => (e) => {
|
||||
|
||||
// If the "other" option is clicked, show the other textbox
|
||||
if(sets === 'detail' && e.target.value === 'other') {
|
||||
this.setState({showOther: true});
|
||||
}
|
||||
|
||||
// If flagging a user, indicate that this is referencing the username rather than the bio
|
||||
if(sets === 'itemType' && e.target.value === 'user') {
|
||||
this.setState({field: 'username'});
|
||||
}
|
||||
|
||||
// Set itemType and field if they are defined in the popupMenu
|
||||
const currentMenu = this.props.getPopupMenu[this.state.step]();
|
||||
if (currentMenu.itemType) {
|
||||
this.setState({itemType: currentMenu.itemType});
|
||||
}
|
||||
if (currentMenu.field) {
|
||||
this.setState({field: currentMenu.field});
|
||||
}
|
||||
|
||||
this.setState({[sets]: e.target.value});
|
||||
}
|
||||
|
||||
onOtherTextChange = (e) => {
|
||||
this.setState({otherText: e.target.value});
|
||||
}
|
||||
|
||||
handleClickOutside () {
|
||||
this.setState({showMenu: false});
|
||||
}
|
||||
|
||||
render () {
|
||||
const {flag, getPopupMenu} = this.props;
|
||||
const flagged = flag && flag.current_user;
|
||||
const popupMenu = getPopupMenu[this.state.step](this.state.itemType);
|
||||
|
||||
return <div className={`${name}-container`}>
|
||||
<button onClick={this.onReportClick} className={`${name}-button`}>
|
||||
{
|
||||
flagged
|
||||
? <span className={`${name}-button-text`}>{lang.t('reported')}</span>
|
||||
: <span className={`${name}-button-text`}>{lang.t('report')}</span>
|
||||
}
|
||||
<i className={`${name}-icon material-icons ${flagged && 'flaggedIcon'}`}
|
||||
style={flagged ? styles.flaggedIcon : {}}
|
||||
aria-hidden={true}>flag</i>
|
||||
</button>
|
||||
{
|
||||
flagged
|
||||
? <span className={`${name}-button-text`}>{lang.t('flagged')}</span>
|
||||
: <span className={`${name}-button-text`}>{lang.t('flag')}</span>
|
||||
this.state.showMenu &&
|
||||
<div className={`${name}-popup`}>
|
||||
<PopupMenu>
|
||||
<div className={`${name}-popup-header`}>{popupMenu.header}</div>
|
||||
{
|
||||
popupMenu.text &&
|
||||
<div className={`${name}-popup-text`}>{popupMenu.text}</div>
|
||||
}
|
||||
{
|
||||
popupMenu.options && <form className={`${name}-popup-form`}>
|
||||
{
|
||||
popupMenu.options.map((option) =>
|
||||
<div key={option.val}>
|
||||
<input
|
||||
className={`${name}-popup-radio`}
|
||||
type="radio"
|
||||
id={option.val}
|
||||
checked={this.state[popupMenu.sets] === option.val}
|
||||
onClick={this.onPopupOptionClick(popupMenu.sets)}
|
||||
value={option.val}/>
|
||||
<label htmlFor={option.val} className={`${name}-popup-radio-label`}>{option.text}</label><br/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
this.state.showOther && <div>
|
||||
<input
|
||||
className={`${name}-other-text`}
|
||||
type="text"
|
||||
id="otherText"
|
||||
onChange={this.onOtherTextChange}
|
||||
value={this.state.otherText}/>
|
||||
<label htmlFor={'otherText'} className={`${name}-popup-radio-label screen-reader-text`}>
|
||||
lang.t('flag-reason')
|
||||
</label><br/>
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
<div className={`${name}-popup-counter`}>
|
||||
{this.state.step + 1} of {getPopupMenu.length}
|
||||
</div>
|
||||
{
|
||||
popupMenu.button && <Button
|
||||
className={`${name}-popup-button`}
|
||||
onClick={this.onPopupContinue}>
|
||||
{popupMenu.button}
|
||||
</Button>
|
||||
}
|
||||
</PopupMenu>
|
||||
</div>
|
||||
}
|
||||
<i className={`${name}-icon material-icons ${flagged && 'flaggedIcon'}`}
|
||||
style={flagged ? styles.flaggedIcon : {}}
|
||||
aria-hidden={true}>flag</i>
|
||||
</button>
|
||||
</div>;
|
||||
};
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default FlagButton;
|
||||
export default onClickOutside(FlagButton);
|
||||
|
||||
const styles = {
|
||||
flaggedIcon: {
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import FlagButton from './FlagButton';
|
||||
import {I18n} from '../coral-framework';
|
||||
import translations from './translations.json';
|
||||
|
||||
const FlagComment = (props) => <FlagButton {...props} getPopupMenu={getPopupMenu} />;
|
||||
|
||||
const getPopupMenu = [
|
||||
() => {
|
||||
return {
|
||||
header: lang.t('step-1-header'),
|
||||
options: [
|
||||
{val: 'user', text: lang.t('flag-username')},
|
||||
{val: 'comments', text: lang.t('flag-comment')}
|
||||
],
|
||||
button: lang.t('continue'),
|
||||
sets: 'itemType'
|
||||
};
|
||||
},
|
||||
(itemType) => {
|
||||
const options = itemType === 'comments' ?
|
||||
[
|
||||
{val: 'I don\'t agree with this comment', text: lang.t('no-agree-comment')},
|
||||
{val: 'This comment is offensive', text: lang.t('comment-offensive')},
|
||||
{val: 'This comment reveals personally identifiable infomration', text: lang.t('personal-info')},
|
||||
{val: 'other', text: lang.t('other')}
|
||||
]
|
||||
: [
|
||||
{val: 'This username is offensive', text: lang.t('username-offensive')},
|
||||
{val: 'I don\'t like this username', text: lang.t('no-like-username')},
|
||||
{val: 'This looks like an ad/marketing', text: lang.t('marketing')},
|
||||
{val: 'other', text: lang.t('other')}
|
||||
];
|
||||
return {
|
||||
header: lang.t('step-2-header'),
|
||||
options,
|
||||
button: lang.t('continue'),
|
||||
sets: 'detail'
|
||||
};
|
||||
},
|
||||
() => {
|
||||
return {
|
||||
header: lang.t('step-3-header'),
|
||||
text: lang.t('thank-you'),
|
||||
button: lang.t('done'),
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
export default FlagComment;
|
||||
|
||||
const lang = new I18n(translations);
|
||||
@@ -1,14 +1,50 @@
|
||||
{
|
||||
"en": {
|
||||
"flag": "Flag",
|
||||
"flagged": "Flagged",
|
||||
"flag-notif": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.",
|
||||
"flag-notif-remove": "Your flag has been removed."
|
||||
"report": "Report",
|
||||
"reported": "Reported",
|
||||
"report-notif": "Thank you for reporting this comment. Our moderation team has been notified and will review it shortly.",
|
||||
"report-notif-remove": "Your report has been removed.",
|
||||
"step-1-header": "Report an issue",
|
||||
"step-2-header": "Help us understand",
|
||||
"step-3-header": "Thank you for your input",
|
||||
"flag-username": "Flag username",
|
||||
"flag-comment": "Flag comment",
|
||||
"continue": "Continue",
|
||||
"done": "Done",
|
||||
"no-agree-comment": "I don't agree with this comment",
|
||||
"comment-offensive": "This comment is offensive",
|
||||
"personal-info": "This comment reveals personally identifiable information",
|
||||
"username-offensive": "This username is offensive",
|
||||
"no-like-username": "I don't like this username",
|
||||
"bio-offensive": "This bio is offensive",
|
||||
"no-like-bio": "I don't like this bio",
|
||||
"marketing": "This looks like an ad/marketing",
|
||||
"thank-you": "We value your safety and feedback. A moderator will review your flag.",
|
||||
"flag-reason": "Reason for flag",
|
||||
"other": "Other"
|
||||
},
|
||||
"es": {
|
||||
"flag": "Marcar",
|
||||
"flagged": "Marcado",
|
||||
"flag-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.",
|
||||
"flag-notif-remove": "Tu marca ha sido eliminada."
|
||||
"report": "Informe",
|
||||
"reported": "Informado",
|
||||
"report-notif": "Gracias por marcar este comentario. Nuestro equipo de moderación ha sido notificado y muy pronto lo va a revisar.",
|
||||
"report-notif-remove": "Tu marca ha sido eliminada.",
|
||||
"step-1-header": "Reportar un problema",
|
||||
"step-2-header": "Ayudanos a entender",
|
||||
"step-3-header": "Gracias por tu participación",
|
||||
"flag-username": "Marcar el nombre de usuario",
|
||||
"flag-comment": "Marcar el comentario",
|
||||
"continue": "Continuar",
|
||||
"done": "hecho",
|
||||
"no-agree-comment": "No estoy de acuerdo con este comentario",
|
||||
"comment-offensive": "Este comentario es ofensivo",
|
||||
"personal-info": "Este comentario muestra información personal",
|
||||
"username-offensive": "Este nombre de usuario es ofensivo",
|
||||
"no-like-username": "No me gusta ese nombre de usuario",
|
||||
"bio-offensive": "Esta bio es ofensiva",
|
||||
"no-like-bio": "No me gusta esta bio",
|
||||
"marketing": "Esto parece una publicidad/marketing",
|
||||
"thank-you": "Nos interesa tu protección y comentarios. Un moderador va a mirar tu marca.",
|
||||
"flag-reason": "Razón por la que marcar",
|
||||
"other": "Otro"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ const LikeButton = ({like, id, postAction, deleteAction, addItem, showSignInDial
|
||||
return;
|
||||
}
|
||||
if (!liked) {
|
||||
postAction(id, 'like', currentUser.id, 'comments')
|
||||
const action = {
|
||||
action_type: 'like'
|
||||
};
|
||||
postAction(id, 'comments', action)
|
||||
.then((action) => {
|
||||
let id = `${action.action_type}_${action.item_id}`;
|
||||
addItem({id, current_user: action, count: like ? like.count + 1 : 1}, 'actions');
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
line-height: 36px;
|
||||
line-height: 28px;
|
||||
vertical-align: middle;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
.popupMenu {
|
||||
display: inline-block;
|
||||
width: inherit;
|
||||
border: solid 1px #999;
|
||||
box-shadow: 3px 3px 5px 0 rgba(0, 0, 0, 0.3);
|
||||
box-sizing: border-box;
|
||||
background: white;
|
||||
border-radius: 3px;
|
||||
padding: 20px 10px;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.popupMenu:before{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: white;
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
bottom: -20px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.popupMenu:after{
|
||||
content: '';
|
||||
border: 10px solid transparent;
|
||||
border-top-color: #999;
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
bottom: -21px;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import styles from './PopupMenu.css';
|
||||
|
||||
export default ({children}) => (
|
||||
<span className={styles.popupMenu}>{children}</span>
|
||||
);
|
||||
@@ -7,4 +7,5 @@ export {default as TabContent} from './components/TabContent';
|
||||
export {default as Button} from './components/Button';
|
||||
export {default as Spinner} from './components/Spinner';
|
||||
export {default as Tooltip} from './components/Tooltip';
|
||||
export {default as PopupMenu} from './components/PopupMenu';
|
||||
export {default as Checkbox} from './components/Checkbox';
|
||||
|
||||
+4
-8
@@ -12,7 +12,9 @@ const ActionSchema = new Schema({
|
||||
action_type: String,
|
||||
item_type: String,
|
||||
item_id: String,
|
||||
user_id: String
|
||||
user_id: String,
|
||||
field: String, // Used when an action references a particular field of an object. (e.g. a flag on a username or bio)
|
||||
detail: String, // Describes the reason for an action (e.g. 'Username is offensive')
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
@@ -35,13 +37,7 @@ ActionSchema.statics.findById = function(id) {
|
||||
* @param {String} action the new action to the comment
|
||||
* @return {Promise}
|
||||
*/
|
||||
ActionSchema.statics.insertUserAction = ({item_id, item_type, user_id, action_type}) => {
|
||||
const action = {
|
||||
item_id,
|
||||
item_type,
|
||||
user_id,
|
||||
action_type
|
||||
};
|
||||
ActionSchema.statics.insertUserAction = (action) => {
|
||||
|
||||
// Create/Update the action.
|
||||
return Action.findOneAndUpdate(action, action, {
|
||||
|
||||
+5
-3
@@ -287,11 +287,13 @@ CommentSchema.statics.pushStatus = (id, status, assigned_by = null) => Comment.u
|
||||
* @param {String} action the new action to the comment
|
||||
* @return {Promise}
|
||||
*/
|
||||
CommentSchema.statics.addAction = (item_id, user_id, action_type) => Action.insertUserAction({
|
||||
CommentSchema.statics.addAction = (item_id, user_id, action_type, field, detail) => Action.insertUserAction({
|
||||
item_id,
|
||||
item_type: 'comment',
|
||||
item_type: 'comments',
|
||||
user_id,
|
||||
action_type
|
||||
action_type,
|
||||
field,
|
||||
detail
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ const uuid = require('uuid');
|
||||
const _ = require('lodash');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const Action = require('./action');
|
||||
|
||||
const Comment = require('./comment');
|
||||
|
||||
@@ -596,3 +597,19 @@ UserService.addBio = (id, bio) => (
|
||||
new: true
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* Add an action to the user.
|
||||
* @param {String} item_id identifier of the user (uuid)
|
||||
* @param {String} user_id user id of the action (uuid)
|
||||
* @param {String} action the new action to the user
|
||||
* @return {Promise}
|
||||
*/
|
||||
UserService.addAction = (item_id, user_id, action_type, field, detail) => Action.insertUserAction({
|
||||
item_id,
|
||||
item_type: 'user',
|
||||
user_id,
|
||||
action_type,
|
||||
field,
|
||||
detail
|
||||
});
|
||||
|
||||
@@ -161,11 +161,13 @@ router.put('/:comment_id/status', authorization.needed('admin'), (req, res, next
|
||||
router.post('/:comment_id/actions', (req, res, next) => {
|
||||
|
||||
const {
|
||||
action_type
|
||||
action_type,
|
||||
field,
|
||||
detail
|
||||
} = req.body;
|
||||
|
||||
Comment
|
||||
.addAction(req.params.comment_id, req.user.id, action_type)
|
||||
.addAction(req.params.comment_id, req.user.id, action_type, field, detail)
|
||||
.then((action) => {
|
||||
res.status(201).json(action);
|
||||
})
|
||||
|
||||
@@ -158,4 +158,21 @@ router.put('/:user_id/bio', (req, res, next) => {
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:user_id/actions', authorization.needed(), (req, res, next) => {
|
||||
const {
|
||||
action_type,
|
||||
field,
|
||||
detail
|
||||
} = req.body;
|
||||
|
||||
User
|
||||
.addAction(req.params.user_id, req.user.id, action_type, field, detail)
|
||||
.then((action) => {
|
||||
res.status(201).json(action);
|
||||
})
|
||||
.catch((err) => {
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -155,7 +155,11 @@ describe('itemActions', () => {
|
||||
describe('postAction', () => {
|
||||
it ('should post an action', () => {
|
||||
fetchMock.post('*', {id: '456'});
|
||||
return actions.postAction('abc', 'flag', '123', 'comments')(store.dispatch)
|
||||
const action = {
|
||||
action_type: 'flag',
|
||||
detail: 'Comment smells funny'
|
||||
};
|
||||
return actions.postAction('abc', 'comments', action)(store.dispatch)
|
||||
.then(response => {
|
||||
expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions');
|
||||
expect(response).to.deep.equal({id:'456'});
|
||||
|
||||
@@ -395,11 +395,12 @@ describe('/api/v1/comments/:comment_id/actions', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/comments/abc/actions')
|
||||
.set(passport.inject({id: '456', roles: ['admin']}))
|
||||
.send({'user_id': '456', 'action_type': 'flag'})
|
||||
.send({'action_type': 'flag', 'detail': 'Comment is too awesome.'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('action_type', 'flag');
|
||||
expect(res.body).to.have.property('detail', 'Comment is too awesome.');
|
||||
expect(res.body).to.have.property('item_id', 'abc');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
const passport = require('../../../passport');
|
||||
|
||||
const app = require('../../../../app');
|
||||
const chai = require('chai');
|
||||
const expect = chai.expect;
|
||||
|
||||
// Setup chai.
|
||||
chai.should();
|
||||
chai.use(require('chai-http'));
|
||||
|
||||
const User = require('../../../../models/user');
|
||||
|
||||
describe('/api/v1/user/:user_id/actions', () => {
|
||||
|
||||
const users = [{
|
||||
displayName: 'Ana',
|
||||
email: 'ana@gmail.com',
|
||||
password: '123'
|
||||
}, {
|
||||
displayName: 'Maria',
|
||||
email: 'maria@gmail.com',
|
||||
password: '123'
|
||||
}];
|
||||
|
||||
beforeEach(() => {
|
||||
return User.createLocalUsers(users);
|
||||
});
|
||||
|
||||
describe('#post', () => {
|
||||
it('it should update actions', () => {
|
||||
return chai.request(app)
|
||||
.post('/api/v1/user/abc/actions')
|
||||
.set(passport.inject({id: '456', roles: ['admin']}))
|
||||
.send({'action_type': 'flag', 'detail': 'Bio is too awesome.'})
|
||||
.then((res) => {
|
||||
expect(res).to.have.status(201);
|
||||
expect(res).to.have.body;
|
||||
expect(res.body).to.have.property('action_type', 'flag');
|
||||
expect(res.body).to.have.property('detail', 'Bio is too awesome.');
|
||||
expect(res.body).to.have.property('item_id', 'abc');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user