diff --git a/src/redux/preview.js b/src/redux/preview.js index 109635a12..2652f1379 100644 --- a/src/redux/preview.js +++ b/src/redux/preview.js @@ -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 diff --git a/src/views/preview/comment/comment.jsx b/src/views/preview/comment/comment.jsx index 5052aa0df..19ca6b131 100644 --- a/src/views/preview/comment/comment.jsx +++ b/src/views/preview/comment/comment.jsx @@ -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; diff --git a/src/views/preview/comment/comment.scss b/src/views/preview/comment/comment.scss index e7bfdffdc..6ebccbe75 100644 --- a/src/views/preview/comment/comment.scss +++ b/src/views/preview/comment/comment.scss @@ -131,6 +131,19 @@ height: 9px; content: ""; } + + &.comment-bubble-deleted { + $deleted-outline: #ff6680; + $deleted-background: rgb(236, 206, 223); + + border-color: $deleted-outline; + background-color: $deleted-background; + + &:before { + border-color: $deleted-outline transparent $deleted-outline $deleted-outline; + background: $deleted-background; + } + } } .comment-content { diff --git a/src/views/preview/comment/top-level-comment.jsx b/src/views/preview/comment/top-level-comment.jsx index a1b96f4ec..f3eb129ad 100644 --- a/src/views/preview/comment/top-level-comment.jsx +++ b/src/views/preview/comment/top-level-comment.jsx @@ -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) diff --git a/src/views/preview/presentation.jsx b/src/views/preview/presentation.jsx index 3cbf44557..85918defa 100644 --- a/src/views/preview/presentation.jsx +++ b/src/views/preview/presentation.jsx @@ -64,6 +64,7 @@ const PreviewPresentation = ({ projectStudios, studios, userOwnsProject, + onDeleteComment, onFavoriteClicked, onLoadMore, onLoveClicked, @@ -318,11 +319,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 && @@ -367,6 +371,7 @@ PreviewPresentation.propTypes = { loved: PropTypes.bool, onAddToStudioClicked: PropTypes.func, onAddToStudioClosed: PropTypes.func, + onDeleteComment: PropTypes.func, onFavoriteClicked: PropTypes.func, onLoadMore: PropTypes.func, onLoveClicked: PropTypes.func, diff --git a/src/views/preview/preview.jsx b/src/views/preview/preview.jsx index b8f1277d5..90aecb34a 100644 --- a/src/views/preview/preview.jsx +++ b/src/views/preview/preview.jsx @@ -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));