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

Merge pull request #87 from weseek/imprv/refactor-comments

Imprv/refactor comments
Yuki Takei 8 лет назад
Родитель
Сommit
7a35c9c8c5

+ 3 - 0
lib/views/crowi-plus/widget/comments.html

@@ -5,6 +5,7 @@
     <h4><i class="fa fa-comments"></i> Comments</h4>
 
     <div class="page-comments-list" id="page-comments-list">
+      {# transplanted to PageComments React component -- 2017.06.02 Yuki Takei
       <div class="page-comments-list-newer collapse" id="page-comments-list-newer"></div>
 
       <a class="page-comments-list-toggle-newer text-center" data-toggle="collapse" href="#page-comments-list-newer"><i class="fa fa-angle-double-up"></i> Comments for Newer Revision <i class="fa fa-angle-double-up"></i></a>
@@ -14,6 +15,7 @@
       <a class="page-comments-list-toggle-older text-center" data-toggle="collapse" href="#page-comments-list-older"><i class="fa fa-angle-double-down"></i> Comments for Older Revision <i class="fa fa-angle-double-down"></i></a>
 
       <div class="page-comments-list-older collapse in" id="page-comments-list-older"></div>
+      #}
     </div>
 
     {% if page and not page.isDeleted() %}
@@ -42,6 +44,7 @@
         </div>
       </div>
     </form>
+    <div id="page-comment-form-behavior"></div>
     {% endif %}
 
   </div>

+ 1 - 0
lib/views/widget/page_side_content.html

@@ -28,6 +28,7 @@
       </div>
     </div>
   </form>
+  <div id="page-comment-form-behavior"></div>
 
   <div class="page-comments-list" id="page-comments-list">
     <div class="page-comments-list-newer collapse" id="page-comments-list-newer"></div>

+ 21 - 3
resource/js/app.js

@@ -8,12 +8,13 @@ import HeaderSearchBox  from './components/HeaderSearchBox';
 import SearchPage       from './components/SearchPage';
 import PageListSearch   from './components/PageListSearch';
 import PageHistory      from './components/PageHistory';
+import PageComments     from './components/PageComments';
+import PageCommentFormBehavior from './components/PageCommentFormBehavior';
 import PageAttachment   from './components/PageAttachment';
 import SeenUserList     from './components/SeenUserList';
 import RevisionPath     from './components/Page/RevisionPath';
 import RevisionUrl      from './components/Page/RevisionUrl';
 import BookmarkButton   from './components/BookmarkButton';
-//import PageComment  from './components/PageComment';
 
 if (!window) {
   window = {};
@@ -21,10 +22,14 @@ if (!window) {
 
 const mainContent = document.querySelector('#content-main');
 let pageId = null;
+let pageRevisionId = null;
+let pageRevisionCreatedAt = null;
 let pagePath;
 let pageContent = null;
 if (mainContent !== null) {
   pageId = mainContent.attributes['data-page-id'].value;
+  pageRevisionId = mainContent.attributes['data-page-revision-id'].value;
+  pageRevisionCreatedAt = +mainContent.attributes['data-page-revision-created'].value;
   pagePath = mainContent.attributes['data-path'].value;
   const rawText = document.getElementById('raw-text-original');
   if (rawText) {
@@ -51,29 +56,42 @@ if (isEnabledPlugins) {
   crowiPlugin.installAll(crowi, crowiRenderer);
 }
 
+/**
+ * define components
+ *  key: id of element
+ *  value: React Element
+ */
 const componentMappings = {
   'search-top': <HeaderSearchBox crowi={crowi} />,
   'search-page': <SearchPage crowi={crowi} />,
   'page-list-search': <PageListSearch crowi={crowi} />,
+  'page-comments-list': <PageComments pageId={pageId} revisionId={pageRevisionId} revisionCreatedAt= {pageRevisionCreatedAt} crowi={crowi} />,
   'page-attachment': <PageAttachment pageId={pageId} pageContent={pageContent} crowi={crowi} />,
 
   //'revision-history': <PageHistory pageId={pageId} />,
-  //'page-comment': <PageComment />,
   'seen-user-list': <SeenUserList pageId={pageId} crowi={crowi} />,
   'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
 };
+// additional definitions if pagePath exists
 if (pagePath) {
   componentMappings['revision-path'] = <RevisionPath pagePath={pagePath} />;
   componentMappings['revision-url'] = <RevisionUrl pageId={pageId} pagePath={pagePath} />;
 }
 
+let componentInstances = {};
 Object.keys(componentMappings).forEach((key) => {
   const elem = document.getElementById(key);
   if (elem) {
-    ReactDOM.render(componentMappings[key], elem);
+    componentInstances[key] = ReactDOM.render(componentMappings[key], elem);
   }
 });
 
+// render components with refs to another component
+const elem = document.getElementById('page-comment-form-behavior');
+if (elem) {
+  ReactDOM.render(<PageCommentFormBehavior crowi={crowi} pageComments={componentInstances['page-comments-list']} />, elem);
+}
+
 // うわーもうー
 $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', function() {
   ReactDOM.render(<PageHistory pageId={pageId} crowi={crowi} />, document.getElementById('revision-history'));

+ 75 - 0
resource/js/components/PageComment/Comment.js

@@ -0,0 +1,75 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import moment from 'moment/src/moment';
+
+import UserPicture from '../User/UserPicture';
+
+/**
+ *
+ * @author Yuki Takei <yuki@weseek.co.jp>
+ *
+ * @export
+ * @class Comment
+ * @extends {React.Component}
+ */
+export default class Comment extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.isCurrentUserIsAuthor = this.isCurrentUserIsAuthor.bind(this);
+    this.isCurrentRevision = this.isCurrentRevision.bind(this);
+    this.getRootClassName = this.getRootClassName.bind(this);
+  }
+
+  isCurrentUserIsAuthor() {
+    return this.props.comment.creator._id === this.props.currentUserId;
+  }
+
+  isCurrentRevision() {
+    return this.props.comment.revision === this.props.currentRevisionId;
+  }
+
+  getRootClassName() {
+    return "page-comment "
+        + (this.isCurrentUserIsAuthor() ? 'page-comment-me' : '')
+        + (this.isCurrentRevision() ? '': 'page-comment-old');
+  }
+
+  getRevisionLabelClassName() {
+    return 'page-comment-revision label '
+        + (this.isCurrentRevision() ? 'label-primary' : 'label-default');
+  }
+
+  render() {
+    const comment = this.props.comment;
+    const creator = comment.creator;
+
+    const rootClassName = this.getRootClassName();
+    const commentDate = moment(comment.createdAt).format('YYYY/MM/DD HH:mm:ss');
+    const revHref = `?revision=${comment.revision}`;
+    const revFirst8Letters = comment.revision.substr(0,8);
+    const revisionLavelClassName = this.getRevisionLabelClassName();
+
+    return (
+      <div className={rootClassName}>
+        <UserPicture user={creator} />
+        <div className="page-comment-main">
+          <div className="page-comment-creator">{creator.username}</div>
+          <div className="page-comment-body">{comment.comment.replace(/(\r\n|\r|\n)/g, '<br>')}</div>
+          <div className="page-comment-meta">
+            {commentDate}&nbsp;
+            <a className={revisionLavelClassName} href={revHref}>{revFirst8Letters}</a>
+          </div>
+        </div>
+      </div>
+    );
+  }
+}
+
+Comment.propTypes = {
+  comment: PropTypes.object.isRequired,
+  currentRevisionId: PropTypes.string.isRequired,
+  currentUserId: PropTypes.string.isRequired,
+};

+ 64 - 0
resource/js/components/PageCommentFormBehavior.js

@@ -0,0 +1,64 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import PageComments from './PageComments';
+
+/**
+ * Set the behavior that post comments to #page-comment-form
+ *
+ * This is transplanted from legacy/crowi.js -- 2017.06.03 Yuki Takei
+ *
+ * @author Yuki Takei <yuki@weseek.co.jp>
+ *
+ * @export
+ * @class PageCommentFormBehavior
+ * @extends {React.Component}
+ */
+export default class PageCommentFormBehavior extends React.Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentWillMount() {
+    const pageComments = this.props.pageComments;
+
+    if (pageComments === undefined) {
+      return;
+    }
+
+    $('#page-comment-form').on('submit', function() {
+      var $button = $('#comment-form-button');
+      $button.attr('disabled', 'disabled');
+      $.post('/_api/comments.add', $(this).serialize(), function(data) {
+        $button.prop('disabled', false);
+        if (data.ok) {
+
+          // reload comments
+          pageComments.init();
+
+          $('#comment-form-comment').val('');
+          $('#comment-form-message').text('');
+        } else {
+          $('#comment-form-message').text(data.error);
+        }
+      }).fail(function(data) {
+        if (data.status !== 200) {
+          $('#comment-form-message').text(data.statusText);
+        }
+      });
+
+      return false;
+    });
+  }
+
+  render() {
+    // render nothing
+    return <div></div>
+  }
+}
+
+PageCommentFormBehavior.propTypes = {
+  pageComments: React.PropTypes.instanceOf(PageComments),
+  crowi: PropTypes.object.isRequired,
+};

+ 133 - 0
resource/js/components/PageComments.js

@@ -0,0 +1,133 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import Comment from './PageComment/Comment';
+
+/**
+ * Load data of comments and render the list of <Comment />
+ *
+ * @author Yuki Takei <yuki@weseek.co.jp>
+ *
+ * @export
+ * @class PageComments
+ * @extends {React.Component}
+ */
+export default class PageComments extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      currentComments: [],
+      newerComments: [],
+      olderComments: [],
+    };
+
+    this.init = this.init.bind(this);
+  }
+
+  componentWillMount() {
+    const pageId = this.props.pageId;
+
+    if (pageId) {
+      this.init();
+    }
+  }
+
+  init() {
+    if (!this.props.pageId) {
+      return ;
+    }
+
+    const pageId = this.props.pageId;
+    const revisionId = this.props.revisionId;
+    const revisionCreatedAt = this.props.revisionCreatedAt;
+
+    this.props.crowi.apiGet('/comments.get', {page_id: pageId})
+    .then(res => {
+      if (res.ok) {
+        let currentComments = [];
+        let newerComments = [];
+        let olderComments = [];
+
+        // divide by revisionId and createdAt
+        res.comments.forEach((comment) => {
+          if (comment.revision == revisionId) {
+            currentComments.push(comment);
+          }
+          else if (Date.parse(comment.createdAt)/1000 > revisionCreatedAt) {
+            newerComments.push(comment);
+          }
+          else {
+            olderComments.push(comment);
+          }
+        });
+        this.setState({currentComments, newerComments, olderComments});
+      }
+    }).catch(err => {
+
+    });
+
+  }
+
+  /**
+   * generate Elements of Comment
+   *
+   * @param {any} comments Array of Comment Model Obj
+   *
+   * @memberOf PageComments
+   */
+  generateCommentElements(comments) {
+    return comments.map((comment) => {
+      return (
+        <Comment key={comment._id} comment={comment}
+          currentUserId={this.props.crowi.me}
+          currentRevisionId={this.props.revisionId} />
+      );
+    });
+  }
+
+  render() {
+    let currentElements = this.generateCommentElements(this.state.currentComments);
+    let newerElements = this.generateCommentElements(this.state.newerComments);
+    let olderElements = this.generateCommentElements(this.state.olderComments);
+
+    let toggleNewer = (newerElements.length === 0)
+      ? <div></div>
+      : (
+        <a className="page-comments-list-toggle-newer text-center" data-toggle="collapse" href="#page-comments-list-newer">
+          <i className="fa fa-angle-double-up"></i> Comments for Newer Revision <i className="fa fa-angle-double-up"></i>
+        </a>
+      )
+    let toggleOlder = (olderElements.length === 0)
+      ? <div></div>
+      : (
+        <a className="page-comments-list-toggle-older text-center" data-toggle="collapse" href="#page-comments-list-older">
+          <i className="fa fa-angle-double-down"></i> Comments for Older Revision <i className="fa fa-angle-double-down"></i>
+        </a>
+      )
+
+    return (
+      <div>
+        <div className="page-comments-list-newer collapse" id="page-comments-list-newer">
+          {newerElements}
+        </div>
+        {toggleNewer}
+        <div className="page-comments-list-current" id="page-comments-list-current">
+          {currentElements}
+        </div>
+        {toggleOlder}
+        <div className="page-comments-list-older collapse in" id="page-comments-list-older">
+          {olderElements}
+        </div>
+      </div>
+    );
+  }
+}
+
+PageComments.propTypes = {
+  pageId: PropTypes.string,
+  revisionId: PropTypes.string,
+  revisionCreatedAt: PropTypes.number,
+  crowi: PropTypes.object.isRequired,
+};

+ 6 - 0
resource/js/legacy/crowi.js

@@ -482,6 +482,10 @@ $(function() {
       });
     }
 
+    /*
+     * transplanted to React components -- 2017.06.02 Yuki Takei
+     *
+
     // omg
     function createCommentHTML(revision, creator, comment, commentedAt) {
       var $comment = $('<div>');
@@ -587,6 +591,8 @@ $(function() {
       return false;
     });
 
+    */
+
     // Like
     var $likeButton = $('.like-button');
     var $likeCount = $('#like-count');