|
@@ -1,9 +1,16 @@
|
|
|
|
|
+/* eslint-disable react/no-multi-comp */
|
|
|
/* eslint-disable react/no-access-state-in-setstate */
|
|
/* eslint-disable react/no-access-state-in-setstate */
|
|
|
import React from 'react';
|
|
import React from 'react';
|
|
|
import PropTypes from 'prop-types';
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
|
|
|
|
|
+import { Subscribe } from 'unstated';
|
|
|
|
|
+
|
|
|
|
|
+import { withTranslation } from 'react-i18next';
|
|
|
import GrowiRenderer from '../util/GrowiRenderer';
|
|
import GrowiRenderer from '../util/GrowiRenderer';
|
|
|
|
|
|
|
|
|
|
+import CommentContainer from './PageComment/CommentContainer';
|
|
|
|
|
+import CommentEditor from './PageComment/CommentEditor';
|
|
|
|
|
+
|
|
|
import Comment from './PageComment/Comment';
|
|
import Comment from './PageComment/Comment';
|
|
|
import DeleteCommentModal from './PageComment/DeleteCommentModal';
|
|
import DeleteCommentModal from './PageComment/DeleteCommentModal';
|
|
|
|
|
|
|
@@ -16,21 +23,20 @@ import DeleteCommentModal from './PageComment/DeleteCommentModal';
|
|
|
* @class PageComments
|
|
* @class PageComments
|
|
|
* @extends {React.Component}
|
|
* @extends {React.Component}
|
|
|
*/
|
|
*/
|
|
|
-export default class PageComments extends React.Component {
|
|
|
|
|
|
|
+class PageComments extends React.Component {
|
|
|
|
|
|
|
|
constructor(props) {
|
|
constructor(props) {
|
|
|
super(props);
|
|
super(props);
|
|
|
|
|
|
|
|
this.state = {
|
|
this.state = {
|
|
|
- // desc order array
|
|
|
|
|
- comments: [],
|
|
|
|
|
-
|
|
|
|
|
isLayoutTypeGrowi: false,
|
|
isLayoutTypeGrowi: false,
|
|
|
|
|
|
|
|
// for deleting comment
|
|
// for deleting comment
|
|
|
commentToDelete: undefined,
|
|
commentToDelete: undefined,
|
|
|
isDeleteConfirmModalShown: false,
|
|
isDeleteConfirmModalShown: false,
|
|
|
errorMessageForDeleting: undefined,
|
|
errorMessageForDeleting: undefined,
|
|
|
|
|
+
|
|
|
|
|
+ showEditorIds: new Set(),
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, { mode: 'comment' });
|
|
this.growiRenderer = new GrowiRenderer(this.props.crowi, this.props.crowiOriginRenderer, { mode: 'comment' });
|
|
@@ -40,11 +46,11 @@ export default class PageComments extends React.Component {
|
|
|
this.deleteComment = this.deleteComment.bind(this);
|
|
this.deleteComment = this.deleteComment.bind(this);
|
|
|
this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this);
|
|
this.showDeleteConfirmModal = this.showDeleteConfirmModal.bind(this);
|
|
|
this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this);
|
|
this.closeDeleteConfirmModal = this.closeDeleteConfirmModal.bind(this);
|
|
|
|
|
+ this.replyButtonClickedHandler = this.replyButtonClickedHandler.bind(this);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
componentWillMount() {
|
|
componentWillMount() {
|
|
|
this.init();
|
|
this.init();
|
|
|
- this.retrieveData = this.retrieveData.bind(this);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
init() {
|
|
init() {
|
|
@@ -55,20 +61,7 @@ export default class PageComments extends React.Component {
|
|
|
const layoutType = this.props.crowi.getConfig().layoutType;
|
|
const layoutType = this.props.crowi.getConfig().layoutType;
|
|
|
this.setState({ isLayoutTypeGrowi: layoutType === 'crowi-plus' || layoutType === 'growi' });
|
|
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 });
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ this.props.commentContainer.retrieveComments();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
confirmToDeleteComment(comment) {
|
|
confirmToDeleteComment(comment) {
|
|
@@ -79,11 +72,8 @@ export default class PageComments extends React.Component {
|
|
|
deleteComment() {
|
|
deleteComment() {
|
|
|
const comment = this.state.commentToDelete;
|
|
const comment = this.state.commentToDelete;
|
|
|
|
|
|
|
|
- this.props.crowi.apiPost('/comments.remove', { comment_id: comment._id })
|
|
|
|
|
- .then((res) => {
|
|
|
|
|
- if (res.ok) {
|
|
|
|
|
- this.findAndSplice(comment);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ this.props.commentContainer.deleteComment(comment)
|
|
|
|
|
+ .then(() => {
|
|
|
this.closeDeleteConfirmModal();
|
|
this.closeDeleteConfirmModal();
|
|
|
})
|
|
})
|
|
|
.catch((err) => {
|
|
.catch((err) => {
|
|
@@ -91,18 +81,6 @@ export default class PageComments extends React.Component {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- findAndSplice(comment) {
|
|
|
|
|
- const comments = this.state.comments;
|
|
|
|
|
-
|
|
|
|
|
- const index = comments.indexOf(comment);
|
|
|
|
|
- if (index < 0) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- comments.splice(index, 1);
|
|
|
|
|
-
|
|
|
|
|
- this.setState({ comments });
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
showDeleteConfirmModal() {
|
|
showDeleteConfirmModal() {
|
|
|
this.setState({ isDeleteConfirmModalShown: true });
|
|
this.setState({ isDeleteConfirmModalShown: true });
|
|
|
}
|
|
}
|
|
@@ -115,6 +93,26 @@ export default class PageComments extends React.Component {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ replyButtonClickedHandler(commentId) {
|
|
|
|
|
+ const ids = this.state.showEditorIds.add(commentId);
|
|
|
|
|
+ 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]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return comments;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* generate Elements of Comment
|
|
* generate Elements of Comment
|
|
|
*
|
|
*
|
|
@@ -122,18 +120,30 @@ export default class PageComments extends React.Component {
|
|
|
*
|
|
*
|
|
|
* @memberOf PageComments
|
|
* @memberOf PageComments
|
|
|
*/
|
|
*/
|
|
|
- generateCommentElements(comments) {
|
|
|
|
|
- return comments.map((comment) => {
|
|
|
|
|
|
|
+ generateCommentElements(comments, replies) {
|
|
|
|
|
+ const commentsWithReplies = this.reorderBasedOnReplies(comments, replies);
|
|
|
|
|
+ return commentsWithReplies.map((comment) => {
|
|
|
|
|
+
|
|
|
|
|
+ const commentId = comment._id;
|
|
|
|
|
+ const showEditor = this.state.showEditorIds.has(commentId);
|
|
|
|
|
+
|
|
|
return (
|
|
return (
|
|
|
- <Comment
|
|
|
|
|
- key={comment._id}
|
|
|
|
|
- comment={comment}
|
|
|
|
|
- currentUserId={this.props.crowi.me}
|
|
|
|
|
- currentRevisionId={this.props.revisionId}
|
|
|
|
|
- deleteBtnClicked={this.confirmToDeleteComment}
|
|
|
|
|
- crowi={this.props.crowi}
|
|
|
|
|
- crowiRenderer={this.growiRenderer}
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <div key={commentId}>
|
|
|
|
|
+ <Comment
|
|
|
|
|
+ comment={comment}
|
|
|
|
|
+ deleteBtnClicked={this.confirmToDeleteComment}
|
|
|
|
|
+ crowiRenderer={this.growiRenderer}
|
|
|
|
|
+ onReplyButtonClicked={() => { this.replyButtonClickedHandler(commentId) }}
|
|
|
|
|
+ crowi={this.props.crowi}
|
|
|
|
|
+ />
|
|
|
|
|
+ { showEditor && (
|
|
|
|
|
+ <CommentEditor
|
|
|
|
|
+ crowi={this.props.crowi}
|
|
|
|
|
+ crowiOriginRenderer={this.props.crowiOriginRenderer}
|
|
|
|
|
+ editorOptions={this.props.editorOptions}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
);
|
|
);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -142,8 +152,11 @@ export default class PageComments extends React.Component {
|
|
|
const currentComments = [];
|
|
const currentComments = [];
|
|
|
const newerComments = [];
|
|
const newerComments = [];
|
|
|
const olderComments = [];
|
|
const olderComments = [];
|
|
|
|
|
+ const currentReplies = [];
|
|
|
|
|
+ const newerReplies = [];
|
|
|
|
|
+ const olderReplies = [];
|
|
|
|
|
|
|
|
- let comments = this.state.comments;
|
|
|
|
|
|
|
+ let comments = this.props.commentContainer.state.comments;
|
|
|
if (this.state.isLayoutTypeGrowi) {
|
|
if (this.state.isLayoutTypeGrowi) {
|
|
|
// replace with asc order array
|
|
// replace with asc order array
|
|
|
comments = comments.slice().reverse(); // non-destructive reverse
|
|
comments = comments.slice().reverse(); // non-destructive reverse
|
|
@@ -155,21 +168,35 @@ export default class PageComments extends React.Component {
|
|
|
comments.forEach((comment) => {
|
|
comments.forEach((comment) => {
|
|
|
// comparing ObjectId
|
|
// comparing ObjectId
|
|
|
// eslint-disable-next-line eqeqeq
|
|
// eslint-disable-next-line eqeqeq
|
|
|
- if (comment.revision == revisionId) {
|
|
|
|
|
- currentComments.push(comment);
|
|
|
|
|
|
|
+ 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) {
|
|
else if (Date.parse(comment.createdAt) / 1000 > revisionCreatedAt) {
|
|
|
- newerComments.push(comment);
|
|
|
|
|
|
|
+ newerReplies.push(comment);
|
|
|
}
|
|
}
|
|
|
else {
|
|
else {
|
|
|
- olderComments.push(comment);
|
|
|
|
|
|
|
+ olderReplies.push(comment);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// generate elements
|
|
// generate elements
|
|
|
- const currentElements = this.generateCommentElements(currentComments);
|
|
|
|
|
- const newerElements = this.generateCommentElements(newerComments);
|
|
|
|
|
- const olderElements = this.generateCommentElements(olderComments);
|
|
|
|
|
|
|
+ const currentElements = this.generateCommentElements(currentComments, currentReplies);
|
|
|
|
|
+ const newerElements = this.generateCommentElements(newerComments, newerReplies);
|
|
|
|
|
+ const olderElements = this.generateCommentElements(olderComments, olderReplies);
|
|
|
// generate blocks
|
|
// generate blocks
|
|
|
const currentBlock = (
|
|
const currentBlock = (
|
|
|
<div className="page-comments-list-current" id="page-comments-list-current">
|
|
<div className="page-comments-list-current" id="page-comments-list-current">
|
|
@@ -247,10 +274,45 @@ export default class PageComments extends React.Component {
|
|
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-PageComments.propTypes = {
|
|
|
|
|
- pageId: PropTypes.string,
|
|
|
|
|
- revisionId: PropTypes.string,
|
|
|
|
|
|
|
+/**
|
|
|
|
|
+ * Wrapper component for using unstated
|
|
|
|
|
+ */
|
|
|
|
|
+class PageCommentsWrapper extends React.Component {
|
|
|
|
|
+
|
|
|
|
|
+ render() {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Subscribe to={[CommentContainer]}>
|
|
|
|
|
+ { commentContainer => (
|
|
|
|
|
+ // eslint-disable-next-line arrow-body-style
|
|
|
|
|
+ <PageComments commentContainer={commentContainer} {...this.props} />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Subscribe>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+PageCommentsWrapper.propTypes = {
|
|
|
|
|
+ crowi: PropTypes.object.isRequired,
|
|
|
|
|
+ crowiOriginRenderer: PropTypes.object.isRequired,
|
|
|
|
|
+ pageId: PropTypes.string.isRequired,
|
|
|
|
|
+ revisionId: PropTypes.string.isRequired,
|
|
|
revisionCreatedAt: PropTypes.number,
|
|
revisionCreatedAt: PropTypes.number,
|
|
|
|
|
+ pagePath: PropTypes.string,
|
|
|
|
|
+ editorOptions: PropTypes.object,
|
|
|
|
|
+ slackChannels: PropTypes.string,
|
|
|
|
|
+};
|
|
|
|
|
+PageComments.propTypes = {
|
|
|
|
|
+ commentContainer: PropTypes.object.isRequired,
|
|
|
|
|
+
|
|
|
crowi: PropTypes.object.isRequired,
|
|
crowi: PropTypes.object.isRequired,
|
|
|
crowiOriginRenderer: 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);
|