/* eslint-disable react/no-access-state-in-setstate */ import React from 'react'; import PropTypes from 'prop-types'; import { Provider } 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 = { // desc order array comments: [], children: {}, isLayoutTypeGrowi: false, // for deleting comment commentToDelete: undefined, isDeleteConfirmModalShown: false, errorMessageForDeleting: undefined, }; 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.replyToComment = this.replyToComment.bind(this); } componentWillMount() { this.init(); this.retrieveData = this.retrieveData.bind(this); } init() { if (!this.props.pageId) { return; } const layoutType = this.props.crowi.getConfig().layoutType; this.setState({ isLayoutTypeGrowi: layoutType === 'crowi-plus' || layoutType === 'growi' }); this.retrieveData(); } /** * Load data of comments and store them in state */ retrieveData() { // get data (desc order array) this.props.crowi.apiGet('/comments.get', { page_id: this.props.pageId }) .then((res) => { if (res.ok) { this.setState({ comments: res.comments }); const tempChildren = {}; res.comments.forEach((comment) => { tempChildren[comment._id] = React.createRef(); }); this.setState({ children: tempChildren }); } }); } confirmToDeleteComment(comment) { this.setState({ commentToDelete: comment }); this.showDeleteConfirmModal(); } replyToComment(comment) { this.state.children[comment._id].toggleEditor(); } deleteComment() { const comment = this.state.commentToDelete; this.props.crowi.apiPost('/comments.remove', { comment_id: comment._id }) .then((res) => { if (res.ok) { this.findAndSplice(comment); } this.closeDeleteConfirmModal(); }) .catch((err) => { this.setState({ errorMessageForDeleting: err.message }); }); } findAndSplice(comment) { const comments = this.state.comments; const index = comments.indexOf(comment); if (index < 0) { return; } comments.splice(index, 1); this.setState({ comments }); } showDeleteConfirmModal() { this.setState({ isDeleteConfirmModalShown: true }); } closeDeleteConfirmModal() { this.setState({ commentToDelete: undefined, isDeleteConfirmModalShown: false, errorMessageForDeleting: undefined, }); } // inserts reply after each corresponding comment reorderBasedOnReplies(comments, replies) { // const connections = this.findConnections(comments, replies); // const replyConnections = this.findConnectionsWithinReplies(replies); const repliesReversed = replies.slice().reverse(); for (let i = 0; i < comments.length; i++) { for (let j = 0; j < repliesReversed.length; j++) { if (repliesReversed[j].replyTo === comments[i]._id) { comments.splice(i + 1, 0, repliesReversed[j]); } } } return comments; } /** * generate Elements of Comment * * @param {any} comments Array of Comment Model Obj * * @memberOf PageComments */ generateCommentElements(comments, replies) { // create unstated container instance const commentContainer = new CommentContainer(this.props.crowi, this.props.pageId, this.props.revisionId); const commentsWithReplies = this.reorderBasedOnReplies(comments, replies); return commentsWithReplies.map((comment) => { return (
{ true && ( )}
); }); } render() { const currentComments = []; const newerComments = []; const olderComments = []; const currentReplies = []; const newerReplies = []; const olderReplies = []; let comments = this.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}
); } } PageComments.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, }; export default withTranslation(null, { withRef: true })(PageComments);