/* eslint-disable react/no-multi-comp */ /* eslint-disable react/no-access-state-in-setstate */ import React from 'react'; import PropTypes from 'prop-types'; import { Subscribe } from 'unstated'; import { withTranslation } from 'react-i18next'; import GrowiRenderer from '../util/GrowiRenderer'; import CommentContainer from './PageComment/CommentContainer'; import CommentEditor from './PageComment/CommentEditor'; import Comment from './PageComment/Comment'; import DeleteCommentModal from './PageComment/DeleteCommentModal'; /** * Load data of comments and render the list of * * @author Yuki Takei * * @export * @class PageComments * @extends {React.Component} */ class PageComments extends React.Component { constructor(props) { super(props); this.state = { isLayoutTypeGrowi: false, // for deleting comment commentToDelete: undefined, isDeleteConfirmModalShown: false, errorMessageForDeleting: undefined, showEditorIds: new Set(), }; this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, { mode: 'comment' }); this.init = this.init.bind(this); this.confirmToDeleteComment = this.confirmToDeleteComment.bind(this); this.deleteComment = this.deleteComment.bind(this); this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this); this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this); this.replyButtonClickedHandler = this.replyButtonClickedHandler.bind(this); } componentWillMount() { this.init(); } init() { if (!this.props.pageId) { return; } const layoutType = this.props.crowi.getConfig().layoutType; this.setState({ isLayoutTypeGrowi: layoutType === 'crowi-plus' || layoutType === 'growi' }); this.props.commentContainer.retrieveComments(); } confirmToDeleteComment(comment) { this.setState({ commentToDelete: comment }); this.showDeleteConfirmModal(); } deleteComment() { const comment = this.state.commentToDelete; this.props.commentContainer.deleteComment(comment) .then(() => { this.closeDeleteConfirmModal(); }) .catch((err) => { this.setState({ errorMessageForDeleting: err.message }); }); } showDeleteConfirmModal() { this.setState({ isDeleteConfirmModalShown: true }); } closeDeleteConfirmModal() { this.setState({ commentToDelete: undefined, isDeleteConfirmModalShown: false, errorMessageForDeleting: undefined, }); } replyButtonClickedHandler(commentId) { const ids = this.state.showEditorIds.add(commentId); this.setState({ showEditorIds: ids }); } // adds replies to specific comment object addRepliesToComments(comments, replies) { const commentsWithReplies = []; const commentsCopy = comments.slice(); commentsCopy.forEach((comment) => { comment.replyList = []; replies.forEach((reply) => { if (reply.replyTo === comment._id) { comment.replyList.push(reply); } }); commentsWithReplies.push(comment); }); return commentsWithReplies; } // returns replies renderReplies(comment) { return comment.replyList.map((reply) => { return (
{ this.replyButtonClickedHandler(reply._id) }} crowi={this.props.crowi} replyTo={comment._id} />
); }); } /** * generate Elements of Comment * * @param {any} comments Array of Comment Model Obj * * @memberOf PageComments */ generateCommentElements(comments, replies) { const commentsWithReplies = this.addRepliesToComments(comments, replies); return commentsWithReplies.map((comment) => { const commentId = comment._id; const showEditor = this.state.showEditorIds.has(commentId); return (
{ this.replyButtonClickedHandler(commentId) }} crowi={this.props.crowi} replyTo={undefined} />
{this.renderReplies(comment)}
{ showEditor && ( )}
); }); } render() { const currentComments = []; const newerComments = []; const olderComments = []; const currentReplies = []; const newerReplies = []; const olderReplies = []; let comments = this.props.commentContainer.state.comments; if (this.state.isLayoutTypeGrowi) { // replace with asc order array comments = comments.slice().reverse(); // non-destructive reverse } // divide by revisionId and createdAt const revisionId = this.props.revisionId; const revisionCreatedAt = this.props.revisionCreatedAt; comments.forEach((comment) => { // comparing ObjectId // eslint-disable-next-line eqeqeq if (comment.replyTo === undefined) { // comment is not a reply if (comment.revision === revisionId) { currentComments.push(comment); } else if (Date.parse(comment.createdAt) / 1000 > revisionCreatedAt) { newerComments.push(comment); } else { olderComments.push(comment); } } else // comment is a reply if (comment.revision === revisionId) { currentReplies.push(comment); } else if (Date.parse(comment.createdAt) / 1000 > revisionCreatedAt) { newerReplies.push(comment); } else { olderReplies.push(comment); } }); // generate elements const currentElements = this.generateCommentElements(currentComments, currentReplies); const newerElements = this.generateCommentElements(newerComments, newerReplies); const olderElements = this.generateCommentElements(olderComments, olderReplies); // generate blocks const currentBlock = (
{currentElements}
); const newerBlock = (
{newerElements}
); const olderBlock = (
{olderElements}
); // generate toggle elements const iconForNewer = (this.state.isLayoutTypeGrowi) ? : ; const toggleNewer = (newerElements.length === 0) ?
: ( {iconForNewer} Comments for Newer Revision {iconForNewer} ); const iconForOlder = (this.state.isLayoutTypeGrowi) ? : ; const toggleOlder = (olderElements.length === 0) ?
: ( {iconForOlder} Comments for Older Revision {iconForOlder} ); // layout blocks const commentsElements = (this.state.isLayoutTypeGrowi) ? (
{olderBlock} {toggleOlder} {currentBlock} {toggleNewer} {newerBlock}
) : (
{newerBlock} {toggleNewer} {currentBlock} {toggleOlder} {olderBlock}
); return (
{commentsElements}
); } } /** * Wrapper component for using unstated */ class PageCommentsWrapper extends React.Component { render() { return ( { commentContainer => ( // eslint-disable-next-line arrow-body-style )} ); } } PageCommentsWrapper.propTypes = { crowi: PropTypes.object.isRequired, crowiOriginRenderer: PropTypes.object.isRequired, pageId: PropTypes.string.isRequired, revisionId: PropTypes.string.isRequired, revisionCreatedAt: PropTypes.number, pagePath: PropTypes.string, editorOptions: PropTypes.object, slackChannels: PropTypes.string, }; PageComments.propTypes = { commentContainer: PropTypes.object.isRequired, crowi: PropTypes.object.isRequired, crowiOriginRenderer: PropTypes.object.isRequired, pageId: PropTypes.string.isRequired, revisionId: PropTypes.string.isRequired, revisionCreatedAt: PropTypes.number, pagePath: PropTypes.string, editorOptions: PropTypes.object, slackChannels: PropTypes.string, }; export default withTranslation(null, { withRef: true })(PageCommentsWrapper);