mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2025-01-05 12:02:29 -05:00
195 lines
6.7 KiB
JavaScript
195 lines
6.7 KiB
JavaScript
const React = require('react');
|
|
const PropTypes = require('prop-types');
|
|
const bindAll = require('lodash.bindall');
|
|
const classNames = require('classnames');
|
|
const FormattedMessage = require('react-intl').FormattedMessage;
|
|
|
|
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
|
|
const Comment = require('./comment.jsx');
|
|
|
|
require('./comment.scss');
|
|
|
|
class TopLevelComment extends React.Component {
|
|
constructor (props) {
|
|
super(props);
|
|
bindAll(this, [
|
|
'handleExpandThread',
|
|
'handleAddComment',
|
|
'handleDeleteReply',
|
|
'handleReportReply',
|
|
'handleRestoreReply'
|
|
]);
|
|
this.state = {
|
|
expanded: this.props.defaultExpanded
|
|
};
|
|
|
|
// A cache of {userId: username, ...} in order to show reply usernames
|
|
this.authorUsernameCache = {};
|
|
}
|
|
|
|
handleExpandThread () {
|
|
this.setState({
|
|
expanded: true
|
|
});
|
|
}
|
|
|
|
handleDeleteReply (replyId) {
|
|
// Only apply topLevelCommentId for deleting replies
|
|
// The top level comment itself just gets passed onDelete directly
|
|
this.props.onDelete(replyId, this.props.id);
|
|
}
|
|
|
|
handleReportReply (replyId) {
|
|
// Only apply topLevelCommentId for reporting replies
|
|
// The top level comment itself just gets passed onReport directly
|
|
this.props.onReport(replyId, this.props.id);
|
|
}
|
|
|
|
handleRestoreReply (replyId) {
|
|
this.props.onRestore(replyId, this.props.id);
|
|
}
|
|
|
|
handleAddComment (comment) {
|
|
this.props.onAddComment(comment, this.props.id);
|
|
}
|
|
|
|
authorUsername (authorId) {
|
|
if (this.authorUsernameCache[authorId]) return this.authorUsernameCache[authorId];
|
|
|
|
// If the cache misses, rebuild it. Every reply has a parent id that is
|
|
// either a reply to this top level comment or to one of the replies.
|
|
this.authorUsernameCache[this.props.author.id] = this.props.author.username;
|
|
const replies = this.props.replies;
|
|
for (let i = 0; i < replies.length; i++) {
|
|
this.authorUsernameCache[replies[i].author.id] = replies[i].author.username;
|
|
}
|
|
// Default to top level author if no author is found from authorId
|
|
// This can happen if there is no commentee_id stored with the comment
|
|
return this.authorUsernameCache[authorId] || this.props.author.username;
|
|
}
|
|
|
|
render () {
|
|
const {
|
|
author,
|
|
canDelete,
|
|
canReply,
|
|
canReport,
|
|
canRestore,
|
|
content,
|
|
datetimeCreated,
|
|
highlightedCommentId,
|
|
id,
|
|
onDelete,
|
|
onReport,
|
|
onRestore,
|
|
replies,
|
|
projectId,
|
|
visibility
|
|
} = this.props;
|
|
|
|
const parentVisible = visibility === 'visible';
|
|
|
|
return (
|
|
<FlexRow className="comment-container">
|
|
<Comment
|
|
highlighted={highlightedCommentId === id}
|
|
projectId={projectId}
|
|
onAddComment={this.handleAddComment}
|
|
{...{
|
|
author,
|
|
content,
|
|
datetimeCreated,
|
|
canDelete,
|
|
canReply,
|
|
canReport,
|
|
canRestore,
|
|
id,
|
|
onDelete,
|
|
onReport,
|
|
onRestore,
|
|
visibility
|
|
}}
|
|
/>
|
|
{replies.length > 0 &&
|
|
<FlexRow
|
|
className={classNames(
|
|
'replies',
|
|
'column',
|
|
{collapsed: !this.state.expanded && replies.length > 3}
|
|
)}
|
|
key={id}
|
|
>
|
|
{(this.state.expanded ? replies : replies.slice(0, 3)).map(reply => (
|
|
<Comment
|
|
author={reply.author}
|
|
canDelete={canDelete}
|
|
canReply={canReply}
|
|
canReport={canReport}
|
|
canRestore={canRestore && parentVisible}
|
|
content={reply.content}
|
|
datetimeCreated={reply.datetime_created}
|
|
highlighted={highlightedCommentId === reply.id}
|
|
id={reply.id}
|
|
key={reply.id}
|
|
parentId={id}
|
|
projectId={projectId}
|
|
replyUsername={this.authorUsername(reply.commentee_id)}
|
|
visibility={reply.visibility}
|
|
onAddComment={this.handleAddComment}
|
|
onDelete={this.handleDeleteReply}
|
|
onReport={this.handleReportReply}
|
|
onRestore={this.handleRestoreReply}
|
|
/>
|
|
))}
|
|
</FlexRow>
|
|
}
|
|
{!this.state.expanded && replies.length > 3 &&
|
|
<a
|
|
className="expand-thread"
|
|
onClick={this.handleExpandThread}
|
|
>
|
|
<FormattedMessage
|
|
id="comments.seeMoreReplies"
|
|
values={{
|
|
repliesCount: replies.length
|
|
}}
|
|
/>
|
|
</a>
|
|
}
|
|
</FlexRow>
|
|
);
|
|
}
|
|
}
|
|
|
|
TopLevelComment.propTypes = {
|
|
author: PropTypes.shape({
|
|
id: PropTypes.number,
|
|
image: PropTypes.string,
|
|
scratchteam: PropTypes.bool,
|
|
username: PropTypes.string
|
|
}),
|
|
canDelete: PropTypes.bool,
|
|
canReply: PropTypes.bool,
|
|
canReport: PropTypes.bool,
|
|
canRestore: PropTypes.bool,
|
|
content: PropTypes.string,
|
|
datetimeCreated: PropTypes.string,
|
|
defaultExpanded: PropTypes.bool,
|
|
deletable: PropTypes.bool,
|
|
highlightedCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
|
|
id: PropTypes.number,
|
|
onAddComment: PropTypes.func,
|
|
onDelete: PropTypes.func,
|
|
onReport: PropTypes.func,
|
|
onRestore: PropTypes.func,
|
|
parentId: PropTypes.number,
|
|
projectId: PropTypes.string,
|
|
replies: PropTypes.arrayOf(PropTypes.object),
|
|
visibility: PropTypes.string
|
|
};
|
|
|
|
TopLevelComment.defaultProps = {
|
|
defaultExpanded: false
|
|
};
|
|
|
|
module.exports = TopLevelComment;
|