Merge pull request from LLK/develop

Stage additional changes for release.
This commit is contained in:
Karishma Chadha 2021-06-02 14:09:45 -04:00 committed by GitHub
commit dab89d0a02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 124 additions and 25 deletions

View file

@ -338,6 +338,7 @@
"comments.cancel": "Cancel",
"comments.lengthWarning": "{remainingCharacters, plural, one {1 character left} other {{remainingCharacters} characters left}}",
"comments.loadMoreReplies": "See more replies",
"comments.replyLimitReached": "This comment thread has reached its limit. To continue commenting, you can start a new thread.",
"comments.status.delbyusr": "Deleted by project owner",
"comments.status.censbyfilter": "Censored by filter",
"comments.status.delbyparentcomment": "Parent comment deleted",

View file

@ -26,6 +26,7 @@ class Comment extends React.Component {
'handleConfirmReport',
'handleCancelReport',
'handlePostReply',
'handleReply',
'handleToggleReplying',
'handleRestore',
'setRef'
@ -49,6 +50,14 @@ class Comment extends React.Component {
this.props.onAddComment(comment);
}
handleReply () {
if (this.props.hasReachedThreadLimit) {
this.props.onReply(this.props.id, (this.props.parentId || this.props.id));
} else {
this.handleToggleReplying();
}
}
handleToggleReplying () {
this.setState({replying: !this.state.replying});
}
@ -220,7 +229,7 @@ class Comment extends React.Component {
{(canReply && visible) ? (
<span
className="comment-reply"
onClick={this.handleToggleReplying}
onClick={this.handleReply}
>
<FormattedMessage id="comments.reply" />
</span>
@ -278,10 +287,12 @@ Comment.propTypes = {
canRestore: PropTypes.bool,
content: PropTypes.string,
datetimeCreated: PropTypes.string,
hasReachedThreadLimit: PropTypes.bool,
highlighted: PropTypes.bool,
id: PropTypes.number,
onAddComment: PropTypes.func,
onDelete: PropTypes.func,
onReply: PropTypes.func,
onReport: PropTypes.func,
onRestore: PropTypes.func,
parentId: PropTypes.number,

View file

@ -284,6 +284,15 @@
}
}
.thread-limit-status {
width: calc(100% - 10rem);
margin-left: auto;
.comment-status-icon {
display: none;
}
}
.compose-disabled {
opacity: .5;
}

View file

@ -6,9 +6,13 @@ const FormattedMessage = require('react-intl').FormattedMessage;
const FlexRow = require('../../../components/flex-row/flex-row.jsx');
const Comment = require('./comment.jsx');
const CommentingStatus = require('../../../components/commenting-status/commenting-status.jsx');
require('./comment.scss');
// Thread limit only applies if hasThreadLimit prop is true
const THREAD_LIMIT = 25;
class TopLevelComment extends React.Component {
constructor (props) {
super(props);
@ -16,11 +20,14 @@ class TopLevelComment extends React.Component {
'handleExpandThread',
'handleAddComment',
'handleDeleteReply',
'handleReplyStatus',
'handleReportReply',
'handleRestoreReply'
]);
this.state = {
expanded: this.props.defaultExpanded
expanded: this.props.defaultExpanded,
threadLimitCommentId: '',
threadLimitParentId: ''
};
// A cache of {userId: username, ...} in order to show reply usernames
@ -55,6 +62,12 @@ class TopLevelComment extends React.Component {
this.props.onAddComment(comment, this.props.id);
}
handleReplyStatus (id, parentId) {
// Send the parentId up to track which thread got "reply" clicked on
if (this.props.onReply) this.props.onReply(parentId);
this.setState({threadLimitCommentId: id, threadLimitParentId: parentId});
}
authorUsername (authorId) {
if (this.authorUsernameCache[authorId]) return this.authorUsernameCache[authorId];
@ -80,6 +93,7 @@ class TopLevelComment extends React.Component {
canRestore,
content,
datetimeCreated,
hasThreadLimit,
highlightedCommentId,
id,
moreRepliesToLoad,
@ -88,17 +102,39 @@ class TopLevelComment extends React.Component {
onRestore,
replies,
postURI,
threadHasReplyStatus,
visibility
} = this.props;
const parentVisible = visibility === 'visible';
// Check whether this comment thread has reached the thread limit
const hasReachedThreadLimit = hasThreadLimit && replies.length >= THREAD_LIMIT;
/*
Check all the following conditions:
- hasReachedThreadLimit: the thread has reached the limit
- threadHasReplyStatus: this thread should be showing the status
(false, if the user just clicked reply elsewhere and another thread/comment stole the status message)
- Use the comment id and parent id of this particular comment in this thread
to see if it has the reply status,
only one comment in a thread can have the status
All of these conditions together ensure that the user only sees one status message on the comments page.
*/
const commentHasReplyStatus = (commentId, commentParentId) =>
hasReachedThreadLimit &&
threadHasReplyStatus &&
(this.state.threadLimitCommentId === commentId) &&
(this.state.threadLimitParentId === commentParentId);
return (
<FlexRow className="comment-container">
<Comment
highlighted={highlightedCommentId === id}
postURI={postURI}
onAddComment={this.handleAddComment}
onReply={this.handleReplyStatus}
{...{
author,
content,
@ -108,6 +144,7 @@ class TopLevelComment extends React.Component {
canReply,
canReport,
canRestore,
hasReachedThreadLimit,
id,
onDelete,
onReport,
@ -115,6 +152,13 @@ class TopLevelComment extends React.Component {
visibility
}}
/>
{commentHasReplyStatus(id, id) &&
<CommentingStatus className="thread-limit-status">
<p>
<FormattedMessage id="comments.replyLimitReached" />
</p>
</CommentingStatus>
}
{replies.length > 0 &&
<FlexRow
className={classNames(
@ -125,27 +169,40 @@ class TopLevelComment extends React.Component {
key={id}
>
{(this.state.expanded ? replies : replies.slice(0, 3)).map(reply => (
<Comment
author={reply.author}
canDelete={canDelete}
canDeleteWithoutConfirm={canDeleteWithoutConfirm}
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}
postURI={postURI}
replyUsername={this.authorUsername(reply.commentee_id)}
visibility={reply.visibility}
onAddComment={this.handleAddComment}
onDelete={this.handleDeleteReply}
onReport={this.handleReportReply}
onRestore={this.handleRestoreReply}
/>
<React.Fragment
key={`reply-and-status-${reply.id}`}
>
<Comment
author={reply.author}
canDelete={canDelete}
canDeleteWithoutConfirm={canDeleteWithoutConfirm}
canReply={canReply}
canReport={canReport}
canRestore={canRestore && parentVisible}
content={reply.content}
datetimeCreated={reply.datetime_created}
hasReachedThreadLimit={hasReachedThreadLimit}
highlighted={highlightedCommentId === reply.id}
id={reply.id}
key={reply.id}
parentId={id}
postURI={postURI}
replyUsername={this.authorUsername(reply.commentee_id)}
visibility={reply.visibility}
onAddComment={this.handleAddComment}
onDelete={this.handleDeleteReply}
onReply={this.handleReplyStatus}
onReport={this.handleReportReply}
onRestore={this.handleRestoreReply}
/>
{commentHasReplyStatus(reply.id, id) &&
<CommentingStatus className="thread-limit-status">
<p>
<FormattedMessage id="comments.reachedThreadLimit" />
</p>
</CommentingStatus>
}
</React.Fragment>
))}
{((!this.state.expanded && replies.length > 3) ||
(this.state.expanded && moreRepliesToLoad)) &&
@ -179,24 +236,29 @@ TopLevelComment.propTypes = {
datetimeCreated: PropTypes.string,
defaultExpanded: PropTypes.bool,
deletable: PropTypes.bool,
hasThreadLimit: PropTypes.bool,
highlightedCommentId: PropTypes.oneOfType([PropTypes.number, PropTypes.bool]),
id: PropTypes.number,
moreRepliesToLoad: PropTypes.bool,
onAddComment: PropTypes.func,
onDelete: PropTypes.func,
onLoadMoreReplies: PropTypes.func,
onReply: PropTypes.func,
onReport: PropTypes.func,
onRestore: PropTypes.func,
parentId: PropTypes.number,
postURI: PropTypes.string,
replies: PropTypes.arrayOf(PropTypes.object),
threadHasReplyStatus: PropTypes.bool,
visibility: PropTypes.string
};
TopLevelComment.defaultProps = {
canDeleteWithoutConfirm: false,
defaultExpanded: false,
moreRepliesToLoad: false
hasThreadLimit: false,
moreRepliesToLoad: false,
threadHasReplyStatus: false
};
module.exports = TopLevelComment;

View file

@ -1,4 +1,4 @@
import React, {useEffect, useRef} from 'react';
import React, {useEffect, useRef, useState} from 'react';
import PropTypes from 'prop-types';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
@ -56,6 +56,18 @@ const StudioComments = ({
if (isAdmin !== wasAdmin) handleResetComments();
}, [isAdmin]);
const [replyStatusCommentId, setReplyStatusCommentId] = useState('');
const hasReplyStatus = function (comment) {
return (
comment.parent_id && comment.parent_id === replyStatusCommentId
) || (comment.id === replyStatusCommentId);
};
const handleReplyStatusChange = function (id) {
setReplyStatusCommentId(id);
};
return (
<div className="studio-compose-container">
<div className="studio-header-container">
@ -74,6 +86,7 @@ const StudioComments = ({
}
{comments.map(comment => (
<TopLevelComment
hasThreadLimit
author={comment.author}
canDelete={canDeleteComment}
canDeleteWithoutConfirm={canDeleteCommentWithoutConfirm}
@ -88,10 +101,13 @@ const StudioComments = ({
parentId={comment.parent_id}
postURI={postURI}
replies={replies && replies[comment.id] ? replies[comment.id] : []}
threadHasReplyStatus={hasReplyStatus(comment)}
visibility={comment.visibility}
onAddComment={handleNewComment}
onDelete={handleDeleteComment}
onRestore={handleRestoreComment}
// eslint-disable-next-line react/jsx-no-bind
onReply={handleReplyStatusChange}
onReport={handleReportComment}
onLoadMoreReplies={handleLoadMoreReplies}
/>