publiclab/plots2

View on GitHub
app/javascript/components/CommentsList.js

Summary

Maintainability
B
5 hrs
Test Coverage
import React from "react";
import PropTypes from "prop-types";

import { makeDeepCopy } from "./helpers";

import Comment from "./Comment";
import CommentForm from "./CommentForm";
import CommentReplies from "./CommentReplies";
import CommentToolbarButton from "./CommentToolbarButton";

const CommentsList = ({
  commentFormsVisibility,
  comments,
  currentUser,
  dispatch,
  handleCreateComment,
  handleDeleteComment,
  handleTextAreaChange,
  handleUpdateComment,
  textAreaValues
}) => {
  // this takes an array of comment JSON, then returns a list of React <Comment /> components
  // replies are nested within parent <Comment /> components
  const generateCommentComponents = (commentsArray) => {
    return commentsArray.map((comment) => {
      // these props are common to all forms
      const commentFormProps = {
        commentId: comment.commentId,
        handleTextAreaChange
      }
  
      // each comment comes with in a edit comment form
      const editFormId = "edit-" + comment.commentId;
      const editCommentForm = <CommentForm 
        commentFormType="edit"
        formId={editFormId}
        handleFormSubmit={handleUpdateComment}
        textAreaValue={textAreaValues[editFormId]}
        {...commentFormProps}
      />;
  
      // each comment comes with a button to toggle edit form visible
      const toggleEditButton = <CommentToolbarButton 
        buttonType="edit"
        icon={<i className="fa fa-pencil"></i>}
        onClick={() => dispatch({ 
          type: "TOGGLE COMMENT FORM VISIBILITY",
          commentFormId: "edit-" + comment.commentId 
        })}
      />;
  
      // and a delete button
      const deleteButton = <CommentToolbarButton
        buttonType="delete"
        icon={<i className='icon fa fa-trash'></i>}
        onClick={() => handleDeleteComment(comment.commentId)}
      />;
  
      const replyFormId = "reply-" + comment.commentId;
      let replies = [];
      let replyCommentForm = null;
      let replySection = [];

      // if comment has replies...
      if (comment.replies && comment.replies.length) {
        // recursively generate <Comment> components for the comment's replies
        replies = generateCommentComponents(comment.replies);
      }

      if (!comment.replyTo) {
        // if the comment is NOT a reply to another comment, then it's a top-level comment
        // generate the reply section here, to avoid passing down props
        //   1. "Reply to this comment..." toggle link
        //   2. reply comment form
        //   3. list of replies

        replyCommentForm = currentUser ?
          <CommentForm
            commentFormType="reply"
            formId={replyFormId}
            handleFormSubmit={handleCreateComment}
            textAreaValue={textAreaValues[replyFormId]}
            {...commentFormProps}
          /> :
          <p><a href="/login">Please login to comment.</a></p>; // placeholder: this should have a parameter like /login?return_to=[nodePath]

        replySection = <CommentReplies 
          commentId={comment.commentId}
          isReplyFormVisible={commentFormsVisibility[replyFormId]}
          dispatch={dispatch}
          replyCommentForm={replyCommentForm}
        >
          {replies}
        </CommentReplies>
      }

      return (
        <Comment 
          key={"comment-" + comment.commentId} 
          comment={comment}
          deleteButton={deleteButton}
          editCommentForm={editCommentForm}
          isEditFormVisible={commentFormsVisibility[editFormId]}
          isReplyFormVisible={comment.replyTo ? null : commentFormsVisibility[replyFormId]}
          toggleEditButton={toggleEditButton}
        >
          {replySection}
        </Comment>
      );
    });
  }

  // here we "nest" the reply comments inside of their parent comments
  const newComments = makeDeepCopy(comments);

  // start by filtering out are parent comments (comments that can take replies)
  let parentComments = newComments
    .filter((comment) => !comment.replyTo)
    .map((comment) => {
      comment.replies = [];
      return comment;
    });

  // make a separate array of all comments that are replies
  let replies = newComments.filter((comment) => comment.replyTo);

  // then nest all those replies into their parent comment
  for (let i = 0; i < replies.length; i++) {
    for (let j = 0; j < parentComments.length; j++) {
      if (parentComments[j].commentId === replies[i].replyTo) {
        parentComments[j].replies.push(replies[i]);
      }
    }
  }

  // now that comments are nested, generate all comment components for display
  const commentComponentsList = generateCommentComponents(parentComments);

  return (
    <div id="comments-list" style={{ marginBottom: "50px" }}>
      {commentComponentsList}
    </div>
  );
};

CommentsList.propTypes = {
  commentFormsVisibility: PropTypes.objectOf(PropTypes.bool).isRequired,
  comments: PropTypes.array.isRequired,
  currentUser: PropTypes.object,
  dispatch: PropTypes.func.isRequired,
  handleCreateComment: PropTypes.func.isRequired,
  handleDeleteComment: PropTypes.func.isRequired,
  handleTextAreaChange: PropTypes.func.isRequired,
  handleUpdateComment: PropTypes.func.isRequired,
  textAreaValues: PropTypes.objectOf(PropTypes.string).isRequired
};

export default CommentsList;