mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-02-17 00:21:20 -05:00
Add comment deleting for project owners
This commit is contained in:
parent
9b94849de6
commit
24b456873b
6 changed files with 101 additions and 7 deletions
|
@ -86,6 +86,15 @@ module.exports.previewReducer = (state, action) => {
|
|||
return Object.assign({}, state, {
|
||||
comments: [...state.comments, ...action.items] // TODO: consider a different way of doing this?
|
||||
});
|
||||
case 'SET_COMMENT_DELETED':
|
||||
return Object.assign({}, state, {
|
||||
comments: state.comments.map(comment => {
|
||||
if (comment.id === action.commentId) {
|
||||
return Object.assign({}, comment, {deleted: true});
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
});
|
||||
case 'SET_REPLIES':
|
||||
return Object.assign({}, state, {
|
||||
replies: merge({}, state.replies, action.replies)
|
||||
|
@ -191,6 +200,11 @@ module.exports.setStudioFetchStatus = (studioId, status) => ({
|
|||
status: status
|
||||
});
|
||||
|
||||
module.exports.setCommentDeleted = commentId => ({
|
||||
type: 'SET_COMMENT_DELETED',
|
||||
commentId: commentId
|
||||
});
|
||||
|
||||
module.exports.getProjectInfo = (id, token) => (dispatch => {
|
||||
const opts = {
|
||||
uri: `/projects/${id}`
|
||||
|
@ -562,6 +576,26 @@ module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
|||
});
|
||||
});
|
||||
|
||||
module.exports.deleteComment = (projectId, commentId, token) => (dispatch => {
|
||||
/* TODO fetching/fetched/error states updates for comment deleting */
|
||||
api({
|
||||
uri: `/proxy/comments/project/${projectId}`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'DELETE',
|
||||
useCsrf: true,
|
||||
json: {
|
||||
id: commentId
|
||||
}
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setCommentDeleted(commentId));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.reportProject = (id, jsonData) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('report', module.exports.Status.FETCHING));
|
||||
// scratchr2 will fail if no thumbnail base64 string provided. We don't yet have
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const React = require('react');
|
||||
const PropTypes = require('prop-types');
|
||||
const classNames = require('classnames');
|
||||
|
||||
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
||||
const Avatar = require('../../../components/avatar/avatar.jsx');
|
||||
|
@ -9,8 +10,11 @@ require('./comment.scss');
|
|||
|
||||
const Comment = ({
|
||||
author,
|
||||
deletable,
|
||||
deleted,
|
||||
content,
|
||||
datetimeCreated,
|
||||
onDelete,
|
||||
id
|
||||
}) => (
|
||||
<div
|
||||
|
@ -27,12 +31,25 @@ const Comment = ({
|
|||
href={`/users/${author.username}`}
|
||||
>{author.username}</a>
|
||||
<div className="action-list">
|
||||
{/* TODO: Hook these up to API calls/logic */}
|
||||
<span className="comment-delete">Delete</span>
|
||||
<span className="comment-report">Report</span>
|
||||
{deletable ? (
|
||||
<span
|
||||
className="comment-delete"
|
||||
onClick={onDelete}
|
||||
>
|
||||
Delete {/* TODO internationalize */}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="comment-report">
|
||||
Report {/* TODO internationalize */}
|
||||
</span>
|
||||
</div>
|
||||
</FlexRow>
|
||||
<div className="comment-bubble">
|
||||
<div
|
||||
className={classNames({
|
||||
'comment-bubble': true,
|
||||
'comment-bubble-deleted': deleted
|
||||
})}
|
||||
>
|
||||
{/* TODO: at the moment, comment content does not properly display
|
||||
* emojis/easter eggs
|
||||
* @user links in replies
|
||||
|
@ -63,7 +80,10 @@ Comment.propTypes = {
|
|||
}),
|
||||
content: PropTypes.string,
|
||||
datetimeCreated: PropTypes.string,
|
||||
id: PropTypes.number
|
||||
deletable: PropTypes.bool,
|
||||
deleted: PropTypes.bool,
|
||||
id: PropTypes.number,
|
||||
onDelete: PropTypes.func
|
||||
};
|
||||
|
||||
module.exports = Comment;
|
||||
|
|
|
@ -131,6 +131,16 @@
|
|||
height: 9px;
|
||||
content: "";
|
||||
}
|
||||
|
||||
&.comment-bubble-deleted {
|
||||
border-color: #FF6680;
|
||||
background-color: rgb(236, 206, 223);
|
||||
|
||||
&:before {
|
||||
border-color: #FF6680 transparent #FF6680 #FF6680;
|
||||
background: rgb(236, 206, 223);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
|
|
|
@ -12,7 +12,8 @@ class TopLevelComment extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'handleExpandThread'
|
||||
'handleExpandThread',
|
||||
'handleDelete'
|
||||
]);
|
||||
this.state = {
|
||||
expanded: false
|
||||
|
@ -25,18 +26,27 @@ class TopLevelComment extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
handleDelete () {
|
||||
this.props.onDelete(this.props.id);
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
author,
|
||||
content,
|
||||
datetimeCreated,
|
||||
deletable,
|
||||
deleted,
|
||||
id,
|
||||
replies
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<FlexRow className="comment-container">
|
||||
<Comment {...{author, content, datetimeCreated, id}} />
|
||||
<Comment
|
||||
onDelete={this.handleDelete}
|
||||
{...{author, content, datetimeCreated, deletable, deleted, id}}
|
||||
/>
|
||||
{replies.length > 0 &&
|
||||
<FlexRow
|
||||
className={classNames(
|
||||
|
@ -51,8 +61,11 @@ class TopLevelComment extends React.Component {
|
|||
author={reply.author}
|
||||
content={reply.content}
|
||||
datetimeCreated={reply.datetime_created}
|
||||
deletable={deletable}
|
||||
deleted={reply.deleted}
|
||||
id={reply.id}
|
||||
key={reply.id}
|
||||
onDelete={this.handleDelete}
|
||||
/>
|
||||
))}
|
||||
</FlexRow>
|
||||
|
@ -76,7 +89,10 @@ TopLevelComment.propTypes = {
|
|||
}),
|
||||
content: PropTypes.string,
|
||||
datetimeCreated: PropTypes.string,
|
||||
deletable: PropTypes.bool,
|
||||
deleted: PropTypes.bool,
|
||||
id: PropTypes.number,
|
||||
onDelete: PropTypes.func,
|
||||
parentId: PropTypes.number,
|
||||
projectId: PropTypes.string,
|
||||
replies: PropTypes.arrayOf(PropTypes.object)
|
||||
|
|
|
@ -64,6 +64,7 @@ const PreviewPresentation = ({
|
|||
projectStudios,
|
||||
studios,
|
||||
userOwnsProject,
|
||||
onDeleteComment,
|
||||
onFavoriteClicked,
|
||||
onLoadMore,
|
||||
onLoveClicked,
|
||||
|
@ -316,11 +317,14 @@ const PreviewPresentation = ({
|
|||
author={comment.author}
|
||||
content={comment.content}
|
||||
datetimeCreated={comment.datetime_created}
|
||||
deletable={userOwnsProject}
|
||||
deleted={comment.deleted}
|
||||
id={comment.id}
|
||||
key={comment.id}
|
||||
parentId={comment.parent_id}
|
||||
projectId={projectId}
|
||||
replies={replies && replies[comment.id] ? replies[comment.id] : []}
|
||||
onDelete={onDeleteComment}
|
||||
/>
|
||||
))}
|
||||
{comments.length < projectInfo.stats.comments &&
|
||||
|
@ -365,6 +369,7 @@ PreviewPresentation.propTypes = {
|
|||
loved: PropTypes.bool,
|
||||
onAddToStudioClicked: PropTypes.func,
|
||||
onAddToStudioClosed: PropTypes.func,
|
||||
onDeleteComment: PropTypes.func,
|
||||
onFavoriteClicked: PropTypes.func,
|
||||
onLoadMore: PropTypes.func,
|
||||
onLoveClicked: PropTypes.func,
|
||||
|
|
|
@ -33,6 +33,7 @@ class Preview extends React.Component {
|
|||
super(props);
|
||||
bindAll(this, [
|
||||
'addEventListeners',
|
||||
'handleDeleteComment',
|
||||
'handleToggleStudio',
|
||||
'handleFavoriteToggle',
|
||||
'handleLoadMore',
|
||||
|
@ -164,6 +165,9 @@ class Preview extends React.Component {
|
|||
});
|
||||
});
|
||||
}
|
||||
handleDeleteComment (id) {
|
||||
this.props.handleDeleteComment(this.state.projectId, id, this.props.user.token);
|
||||
}
|
||||
handleReportClick () {
|
||||
this.setState({reportOpen: true});
|
||||
}
|
||||
|
@ -338,6 +342,7 @@ class Preview extends React.Component {
|
|||
userOwnsProject={this.props.userOwnsProject}
|
||||
onAddToStudioClicked={this.handleAddToStudioClick}
|
||||
onAddToStudioClosed={this.handleAddToStudioClose}
|
||||
onDeleteComment={this.handleDeleteComment}
|
||||
onFavoriteClicked={this.handleFavoriteToggle}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
onLoveClicked={this.handleLoveToggle}
|
||||
|
@ -393,6 +398,7 @@ Preview.propTypes = {
|
|||
getProjectStudios: PropTypes.func.isRequired,
|
||||
getRemixes: PropTypes.func.isRequired,
|
||||
getTopLevelComments: PropTypes.func.isRequired,
|
||||
handleDeleteComment: PropTypes.func,
|
||||
handleLogIn: PropTypes.func,
|
||||
handleLogOut: PropTypes.func,
|
||||
handleOpenRegistration: PropTypes.func,
|
||||
|
@ -519,6 +525,9 @@ const mapStateToProps = state => {
|
|||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleDeleteComment: (projectId, commentId, token) => {
|
||||
dispatch(previewActions.deleteComment(projectId, commentId, token));
|
||||
},
|
||||
handleOpenRegistration: event => {
|
||||
event.preventDefault();
|
||||
dispatch(navigationActions.setRegistrationOpen(true));
|
||||
|
|
Loading…
Reference in a new issue