Просмотр исходного кода

Merge pull request #982 from weseek/feat/thread_comments_frontend

formatting
Yuki Takei 6 лет назад
Родитель
Сommit
52836e7e4f

+ 57 - 35
src/client/js/components/PageComment/Comment.jsx

@@ -1,7 +1,6 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import Button from 'react-bootstrap/es/Button';
 import dateFnsFormat from 'date-fns/format';
 
 import RevisionBody from '../Page/RevisionBody';
@@ -33,7 +32,6 @@ export default class Comment extends React.Component {
     this.getRevisionLabelClassName = this.getRevisionLabelClassName.bind(this);
     this.deleteBtnClickedHandler = this.deleteBtnClickedHandler.bind(this);
     this.renderHtml = this.renderHtml.bind(this);
-    this.replyBtnClickedHandler = this.replyBtnClickedHandler.bind(this);
   }
 
   componentWillMount() {
@@ -71,10 +69,6 @@ export default class Comment extends React.Component {
     this.props.deleteBtnClicked(this.props.comment);
   }
 
-  replyBtnClickedHandler() {
-    this.props.onReplyButtonClicked(this.props.comment);
-  }
-
   renderRevisionBody() {
     const config = this.props.crowi.getConfig();
     const isMathJaxEnabled = !!config.env.MATHJAX;
@@ -119,6 +113,24 @@ export default class Comment extends React.Component {
 
   }
 
+  renderReplies() {
+    return this.props.replyList.map((reply) => {
+      return (
+        <div key={reply._id} className="col-xs-offset-1 col-xs-11 col-sm-offset-1 col-sm-11 col-md-offset-1 col-md-11 col-lg-offset-1 col-lg-11">
+          <Comment
+            comment={reply}
+            deleteBtnClicked={this.props.deleteBtnClicked}
+            crowiRenderer={this.props.crowiRenderer}
+            crowi={this.props.crowi}
+            replyList={[]}
+            revisionCreatedAt={this.props.revisionCreatedAt}
+            revisionId={this.props.revisionId}
+          />
+        </div>
+      );
+    });
+  }
+
   render() {
     const comment = this.props.comment;
     const creator = comment.creator;
@@ -131,36 +143,45 @@ export default class Comment extends React.Component {
     const revFirst8Letters = comment.revision.substr(-8);
     const revisionLavelClassName = this.getRevisionLabelClassName();
 
+    const revisionId = this.props.revisionId;
+    const revisionCreatedAt = this.props.revisionCreatedAt;
+    let isNewer;
+    if (comment.revision === revisionId) {
+      isNewer = 'page-comments-list-current';
+    }
+    else if (Date.parse(comment.createdAt) / 1000 > revisionCreatedAt) {
+      isNewer = 'page-comments-list-newer';
+    }
+    else {
+      isNewer = 'page-comments-list-older';
+    }
+
+
     return (
-      <div className={rootClassName}>
-        <UserPicture user={creator} />
-        <div className="page-comment-main">
-          <div className="page-comment-creator">
-            <Username user={creator} />
-          </div>
-          <div className="page-comment-body">{commentBody}</div>
-          <div className="page-comment-reply text-right">
-            {
-              comment.replyTo === undefined
-              && (
-                <Button
-                  type="button"
-                  className="fcbtn btn btn-primary btn-sm btn-success btn-rounded btn-1b"
-                  onClick={this.replyBtnClickedHandler}
-                >
-                  Reply
-                </Button>
-              )
-            }
+      <div>
+        <div className={isNewer}>
+          <div className={rootClassName}>
+            <UserPicture user={creator} />
+            <div className="page-comment-main">
+              <div className="page-comment-creator">
+                <Username user={creator} />
+              </div>
+              <div className="page-comment-body">{commentBody}</div>
+              <div className="page-comment-meta">
+                {commentDate}&nbsp;
+                <a className={revisionLavelClassName} href={revHref}>{revFirst8Letters}</a>
+              </div>
+              <div className="page-comment-control">
+                <button type="button" className="btn btn-link" onClick={this.deleteBtnClickedHandler}>
+                  <i className="ti-close"></i>
+                </button>
+              </div>
+            </div>
           </div>
-          <div className="page-comment-meta">
-            {commentDate}&nbsp;
-            <a className={revisionLavelClassName} href={revHref}>{revFirst8Letters}</a>
-          </div>
-          <div className="page-comment-control">
-            <button type="button" className="btn btn-link" onClick={this.deleteBtnClickedHandler}>
-              <i className="ti-close"></i>
-            </button>
+        </div>
+        <div className="container-fluid">
+          <div className="row">
+            {this.renderReplies()}
           </div>
         </div>
       </div>
@@ -173,7 +194,8 @@ Comment.propTypes = {
   comment: PropTypes.object.isRequired,
   crowiRenderer: PropTypes.object.isRequired,
   deleteBtnClicked: PropTypes.func.isRequired,
-  onReplyButtonClicked: PropTypes.func.isRequired,
   crowi: PropTypes.object.isRequired,
   revisionId: PropTypes.string,
+  replyList: PropTypes.array,
+  revisionCreatedAt: PropTypes.number,
 };

+ 5 - 2
src/client/js/components/PageComment/CommentEditor.jsx

@@ -106,7 +106,7 @@ class CommentEditor extends React.Component {
     this.props.commentContainer.postComment(
       this.state.comment,
       this.state.isMarkdown,
-      this.props.replyTo, // TODO set replyTo
+      this.props.replyTo,
       this.state.isSlackEnabled,
       this.state.slackChannels,
     )
@@ -121,6 +121,7 @@ class CommentEditor extends React.Component {
         });
         // reset value
         this.editor.setValue('');
+        this.props.commentButtonClickedHandler(this.props.replyTo);
       })
       .catch((err) => {
         const errorMessage = err.message || 'An unknown error occured when posting comment';
@@ -225,7 +226,7 @@ class CommentEditor extends React.Component {
     );
 
     return (
-      <div>
+      <div className="form page-comment-form">
 
         { username
           && (
@@ -344,6 +345,7 @@ CommentEditor.propTypes = {
   editorOptions: PropTypes.object,
   slackChannels: PropTypes.string,
   replyTo: PropTypes.string,
+  commentButtonClickedHandler: PropTypes.func.isRequired,
 };
 CommentEditorWrapper.propTypes = {
   crowi: PropTypes.object.isRequired,
@@ -351,6 +353,7 @@ CommentEditorWrapper.propTypes = {
   editorOptions: PropTypes.object,
   slackChannels: PropTypes.string,
   replyTo: PropTypes.string,
+  commentButtonClickedHandler: PropTypes.func.isRequired,
 };
 
 export default CommentEditorWrapper;

+ 30 - 7
src/client/js/components/PageComment/CommentEditorLazyRenderer.jsx

@@ -2,6 +2,8 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import CommentEditor from './CommentEditor';
 
+import UserPicture from '../User/UserPicture';
+
 export default class CommentEditorLazyRenderer extends React.Component {
 
   constructor(props) {
@@ -29,17 +31,38 @@ export default class CommentEditorLazyRenderer extends React.Component {
   }
 
   render() {
+    const crowi = this.props.crowi;
+    const username = crowi.me;
+    const user = crowi.findUser(username);
+    const isLayoutTypeGrowi = this.state.isLayoutTypeGrowi;
     return (
       <React.Fragment>
         { !this.state.isEditorShown
           && (
-          <button
-            type="button"
-            className={`btn btn-lg ${this.state.isLayoutTypeGrowi ? 'btn-link' : 'btn-primary'} center-block`}
-            onClick={this.showCommentFormBtnClickHandler}
-          >
-            <i className="icon-bubble"></i> Add Comment
-          </button>
+          <div className="form page-comment-form">
+            { username
+              && (
+                <div className="comment-form">
+                  { isLayoutTypeGrowi
+                  && (
+                    <div className="comment-form-user">
+                      <UserPicture user={user} />
+                    </div>
+                  )
+                  }
+                  <div className="comment-form-main">
+                    <button
+                      type="button"
+                      className={`btn btn-lg ${this.state.isLayoutTypeGrowi ? 'btn-link' : 'btn-primary'} center-block`}
+                      onClick={this.showCommentFormBtnClickHandler}
+                    >
+                      <i className="icon-bubble"></i> Add Comment
+                    </button>
+                  </div>
+                </div>
+              )
+            }
+          </div>
           )
         }
         { this.state.isEditorShown

+ 66 - 104
src/client/js/components/PageComments.jsx

@@ -2,6 +2,7 @@
 /* eslint-disable react/no-access-state-in-setstate */
 import React from 'react';
 import PropTypes from 'prop-types';
+import Button from 'react-bootstrap/es/Button';
 
 import { Subscribe } from 'unstated';
 
@@ -47,6 +48,7 @@ class PageComments extends React.Component {
     this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this);
     this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this);
     this.replyButtonClickedHandler = this.replyButtonClickedHandler.bind(this);
+    this.commentButtonClickedHandler = this.commentButtonClickedHandler.bind(this);
   }
 
   componentWillMount() {
@@ -98,19 +100,24 @@ class PageComments extends React.Component {
     this.setState({ showEditorIds: ids });
   }
 
-  // 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]);
-        }
+  commentButtonClickedHandler(commentId) {
+    this.setState((prevState) => {
+      prevState.showEditorIds.delete(commentId);
+      return {
+        showEditorIds: prevState.showEditorIds,
+      };
+    });
+  }
+
+  // adds replies to specific comment object
+  addRepliesToComments(comment, replies) {
+    const replyList = [];
+    replies.forEach((reply) => {
+      if (reply.replyTo === comment._id) {
+        replyList.push(reply);
       }
-    }
-    return comments;
+    });
+    return replyList;
   }
 
   /**
@@ -121,11 +128,14 @@ class PageComments extends React.Component {
    * @memberOf PageComments
    */
   generateCommentElements(comments, replies) {
-    const commentsWithReplies = this.reorderBasedOnReplies(comments, replies);
-    return commentsWithReplies.map((comment) => {
+    return comments.map((comment) => {
 
       const commentId = comment._id;
       const showEditor = this.state.showEditorIds.has(commentId);
+      const crowi = this.props.crowi;
+      const username = crowi.me;
+
+      const replyList = this.addRepliesToComments(comment, replies);
 
       return (
         <div key={commentId}>
@@ -133,18 +143,45 @@ class PageComments extends React.Component {
             comment={comment}
             deleteBtnClicked={this.confirmToDeleteComment}
             crowiRenderer={this.growiRenderer}
-            onReplyButtonClicked={() => { this.replyButtonClickedHandler(commentId) }}
             crowi={this.props.crowi}
+            replyList={replyList}
+            revisionCreatedAt={this.props.revisionCreatedAt}
+            revisionId={this.props.revisionId}
           />
-          { showEditor && (
-            <CommentEditor
-              crowi={this.props.crowi}
-              crowiOriginRenderer={this.props.crowiOriginRenderer}
-              editorOptions={this.props.editorOptions}
-              slackChannels={this.props.slackChannels}
-              replyTo={commentId}
-            />
-          )}
+          <div className="container-fluid">
+            <div className="row">
+              <div className="col-xs-offset-1 col-xs-11 col-sm-offset-1 col-sm-11 col-md-offset-1 col-md-11 col-lg-offset-1 col-lg-11">
+                { !showEditor && (
+                  <div>
+                    { username
+                    && (
+                      <div className="col-xs-offset-6 col-sm-offset-6 col-md-offset-6 col-lg-offset-6">
+                        <Button
+                          bsStyle="primary"
+                          className="fcbtn btn btn-sm btn-primary btn-outline btn-rounded btn-1b"
+                          onClick={() => { return this.replyButtonClickedHandler(commentId) }}
+                        >
+                          <i className="icon-bubble"></i> Reply
+                        </Button>
+                      </div>
+                    )
+                  }
+                  </div>
+                )}
+                { showEditor && (
+                  <CommentEditor
+                    crowi={this.props.crowi}
+                    crowiOriginRenderer={this.props.crowiOriginRenderer}
+                    editorOptions={this.props.editorOptions}
+                    slackChannels={this.props.slackChannels}
+                    replyTo={commentId}
+                    commentButtonClickedHandler={this.commentButtonClickedHandler}
+                  />
+                )}
+              </div>
+            </div>
+          </div>
+          <br />
         </div>
       );
     });
@@ -152,11 +189,7 @@ class PageComments extends React.Component {
 
   render() {
     const currentComments = [];
-    const newerComments = [];
-    const olderComments = [];
     const currentReplies = [];
-    const newerReplies = [];
-    const olderReplies = [];
 
     let comments = this.props.commentContainer.state.comments;
     if (this.state.isLayoutTypeGrowi) {
@@ -164,100 +197,29 @@ class PageComments extends React.Component {
       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);
-        }
+      // comment is not a reply
+        currentComments.push(comment);
       }
-      else
+      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 = (
       <div className="page-comments-list-current" id="page-comments-list-current">
         {currentElements}
       </div>
     );
-    const newerBlock = (
-      <div className="page-comments-list-newer collapse in" id="page-comments-list-newer">
-        {newerElements}
-      </div>
-    );
-    const olderBlock = (
-      <div className="page-comments-list-older collapse in" id="page-comments-list-older">
-        {olderElements}
-      </div>
-    );
-
-    // generate toggle elements
-    const iconForNewer = (this.state.isLayoutTypeGrowi)
-      ? <i className="fa fa-angle-double-down"></i>
-      : <i className="fa fa-angle-double-up"></i>;
-    const toggleNewer = (newerElements.length === 0)
-      ? <div></div>
-      : (
-        <a className="page-comments-list-toggle-newer text-center" data-toggle="collapse" href="#page-comments-list-newer">
-          {iconForNewer} Comments for Newer Revision {iconForNewer}
-        </a>
-      );
-    const iconForOlder = (this.state.isLayoutTypeGrowi)
-      ? <i className="fa fa-angle-double-up"></i>
-      : <i className="fa fa-angle-double-down"></i>;
-    const toggleOlder = (olderElements.length === 0)
-      ? <div></div>
-      : (
-        <a className="page-comments-list-toggle-older text-center" data-toggle="collapse" href="#page-comments-list-older">
-          {iconForOlder} Comments for Older Revision {iconForOlder}
-        </a>
-      );
 
     // layout blocks
-    const commentsElements = (this.state.isLayoutTypeGrowi)
-      ? (
-        <div>
-          {olderBlock}
-          {toggleOlder}
-          {currentBlock}
-          {toggleNewer}
-          {newerBlock}
-        </div>
-      )
-      : (
-        <div>
-          {newerBlock}
-          {toggleNewer}
-          {currentBlock}
-          {toggleOlder}
-          {olderBlock}
-        </div>
-      );
+    const commentsElements = (<div>{currentBlock}</div>);
 
     return (
       <div>