Add restore functionality to comments for admins

This commit is contained in:
Paul Kaplan 2018-10-16 10:00:38 -04:00
parent b0ac4018ee
commit 592c0e5703
7 changed files with 87 additions and 16 deletions

View file

@ -206,6 +206,7 @@
"comments.report": "Report",
"comments.delete": "Delete",
"comments.restore": "Restore",
"comments.reportModal.title": "Report Comment",
"comments.reportModal.reported": "The comment has been reported, and the Scratch Team has been notified.",
"comments.reportModal.prompt": "Are you sure you want to report this comment?",

View file

@ -246,6 +246,15 @@ module.exports.setCommentReported = (commentId, topLevelCommentId) => ({
}
});
module.exports.setCommentRestored = (commentId, topLevelCommentId) => ({
type: 'UPDATE_COMMENT',
commentId: commentId,
topLevelCommentId: topLevelCommentId,
comment: {
visibility: 'visible'
}
});
module.exports.addNewComment = (comment, topLevelCommentId) => ({
type: 'ADD_NEW_COMMENT',
comment: comment,
@ -659,6 +668,22 @@ module.exports.reportComment = (projectId, commentId, topLevelCommentId, token)
});
});
module.exports.restoreComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
api({
uri: `/proxy/admin/project/${projectId}/comment/${commentId}/undelete`,
authentication: token,
withCredentials: true,
method: 'PUT',
useCsrf: true
}, (err, body, res) => {
if (err || res.statusCode !== 200) {
log.error(err || res.body);
return;
}
dispatch(module.exports.setCommentRestored(commentId, topLevelCommentId));
});
});
module.exports.reportProject = (id, jsonData, token) => (dispatch => {
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
// scratchr2 will fail if no thumbnail base64 string provided. We don't yet have

View file

@ -13,10 +13,6 @@ const ReportCommentModal = require('../../../components/modal/comments/report-co
require('./comment.scss');
const CommentVisibility = {
VISIBLE: 'visible' // Has to match the server response for visibility
};
class Comment extends React.Component {
constructor (props) {
super(props);
@ -28,7 +24,8 @@ class Comment extends React.Component {
'handleConfirmReport',
'handleCancelReport',
'handlePostReply',
'handleToggleReplying'
'handleToggleReplying',
'handleRestore'
]);
this.state = {
deleting: false,
@ -64,6 +61,10 @@ class Comment extends React.Component {
this.setState({reporting: true});
}
handleRestore () {
this.props.onRestore(this.props.id);
}
handleConfirmReport () {
this.setState({
reporting: false,
@ -93,7 +94,7 @@ class Comment extends React.Component {
visibility
} = this.props;
const visible = visibility === CommentVisibility.VISIBLE;
const visible = visibility === 'visible';
return (
<div
@ -128,10 +129,19 @@ class Comment extends React.Component {
</span>
</React.Fragment>
) : (
<React.Fragment>
<span className="comment-visibility">
<FormattedMessage id={`comments.status.${visibility}`} />
{/* TODO restore action will go here */}
</span>
{this.props.onRestore && (
<span
className="comment-restore"
onClick={this.handleRestore}
>
<FormattedMessage id="comments.restore" />
</span>
)}
</React.Fragment>
)}
</div>
</FlexRow>
@ -210,6 +220,7 @@ Comment.propTypes = {
onAddComment: PropTypes.func,
onDelete: PropTypes.func,
onReport: PropTypes.func,
onRestore: PropTypes.func,
projectId: PropTypes.string,
visibility: PropTypes.string
};

View file

@ -78,7 +78,8 @@
}
.comment-delete,
.comment-report {
.comment-report,
.comment-restore {
opacity: .5;
font-size: .75rem;
font-weight: 500;
@ -119,6 +120,18 @@
vertical-align: -.125rem;
}
}
.comment-restore {
margin-left: 1rem;
&:before {
margin-right: .25rem;
background-image: url("/svgs/project/restore-gray.svg");
width: .75rem;
height: .75rem;
vertical-align: -.125rem;
}
}
}
.avatar {

View file

@ -16,7 +16,8 @@ class TopLevelComment extends React.Component {
'handleExpandThread',
'handleAddComment',
'handleDeleteReply',
'handleReportReply'
'handleReportReply',
'handleRestoreReply'
]);
this.state = {
expanded: false
@ -29,16 +30,20 @@ class TopLevelComment extends React.Component {
});
}
handleDeleteReply (commentId) {
handleDeleteReply (replyId) {
// Only apply topLevelCommentId for deleting replies
// The top level comment itself just gets passed onDelete directly
this.props.onDelete(commentId, this.props.id);
this.props.onDelete(replyId, this.props.id);
}
handleReportReply (commentId) {
handleReportReply (replyId) {
// Only apply topLevelCommentId for reporting replies
// The top level comment itself just gets passed onReport directly
this.props.onReport(commentId, this.props.id);
this.props.onReport(replyId, this.props.id);
}
handleRestoreReply (replyId) {
this.props.onRestore(replyId, this.props.id);
}
handleAddComment (comment) {
@ -55,6 +60,7 @@ class TopLevelComment extends React.Component {
id,
onDelete,
onReport,
onRestore,
replies,
projectId,
visibility
@ -74,6 +80,7 @@ class TopLevelComment extends React.Component {
id,
onDelete,
onReport,
onRestore,
visibility
}}
/>
@ -100,6 +107,7 @@ class TopLevelComment extends React.Component {
onAddComment={this.handleAddComment}
onDelete={this.handleDeleteReply}
onReport={this.handleReportReply}
onRestore={this.handleRestoreReply}
/>
))}
</FlexRow>
@ -136,6 +144,7 @@ TopLevelComment.propTypes = {
onAddComment: PropTypes.func,
onDelete: PropTypes.func,
onReport: PropTypes.func,
onRestore: PropTypes.func,
parentId: PropTypes.number,
projectId: PropTypes.string,
replies: PropTypes.arrayOf(PropTypes.object),

View file

@ -78,6 +78,7 @@ const PreviewPresentation = ({
onReportClose,
onReportComment,
onReportSubmit,
onRestoreComment,
onAddToStudioClicked,
onAddToStudioClosed,
onToggleStudio,
@ -375,6 +376,7 @@ const PreviewPresentation = ({
onAddComment={onAddComment}
onDelete={onDeleteComment}
onReport={onReportComment}
onRestore={onRestoreComment}
/>
))}
{comments.length < projectInfo.stats.comments &&
@ -431,6 +433,7 @@ PreviewPresentation.propTypes = {
onReportClose: PropTypes.func.isRequired,
onReportComment: PropTypes.func.isRequired,
onReportSubmit: PropTypes.func.isRequired,
onRestoreComment: PropTypes.func,
onSeeInside: PropTypes.func,
onShare: PropTypes.func,
onToggleComments: PropTypes.func,

View file

@ -44,6 +44,7 @@ class Preview extends React.Component {
'handleReportClose',
'handleReportComment',
'handleReportSubmit',
'handleRestoreComment',
'handleAddToStudioClick',
'handleAddToStudioClose',
'handleSeeInside',
@ -188,6 +189,9 @@ class Preview extends React.Component {
handleReportComment (id, topLevelCommentId) {
this.props.handleReportComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleRestoreComment (id, topLevelCommentId) {
this.props.handleRestoreComment(this.state.projectId, id, topLevelCommentId, this.props.user.token);
}
handleReportClick () {
this.setState({reportOpen: true});
}
@ -379,6 +383,7 @@ class Preview extends React.Component {
onReportClose={this.handleReportClose}
onReportComment={this.handleReportComment}
onReportSubmit={this.handleReportSubmit}
onRestoreComment={this.props.isAdmin && this.handleRestoreComment}
onSeeInside={this.handleSeeInside}
onShare={this.handleShare}
onToggleComments={this.handleToggleComments}
@ -448,6 +453,7 @@ Preview.propTypes = {
handleLogOut: PropTypes.func,
handleOpenRegistration: PropTypes.func,
handleReportComment: PropTypes.func,
handleRestoreComment: PropTypes.func,
handleToggleLoginOpen: PropTypes.func,
isAdmin: PropTypes.bool,
isEditable: PropTypes.bool,
@ -588,6 +594,9 @@ const mapDispatchToProps = dispatch => ({
handleReportComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.reportComment(projectId, commentId, topLevelCommentId, token));
},
handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => {
dispatch(previewActions.restoreComment(projectId, commentId, topLevelCommentId, token));
},
handleOpenRegistration: event => {
event.preventDefault();
dispatch(navigationActions.setRegistrationOpen(true));