*
* @export
* @class PageComments
* @extends {React.Component}
*/
class PageComments extends React.Component {
constructor(props) {
super(props);
this.state = {
isLayoutTypeGrowi: false,
// for deleting comment
commentToDelete: undefined,
isDeleteConfirmModalShown: false,
errorMessageForDeleting: undefined,
showEditorIds: new Set(),
};
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.replyButtonClickedHandler = this.replyButtonClickedHandler.bind(this);
}
componentWillMount() {
this.init();
}
init() {
if (!this.props.pageId) {
return;
}
const layoutType = this.props.crowi.getConfig().layoutType;
this.setState({ isLayoutTypeGrowi: layoutType === 'crowi-plus' || layoutType === 'growi' });
this.props.commentContainer.retrieveComments();
}
confirmToDeleteComment(comment) {
this.setState({ commentToDelete: comment });
this.showDeleteConfirmModal();
}
deleteComment() {
const comment = this.state.commentToDelete;
this.props.commentContainer.deleteComment(comment)
.then(() => {
this.closeDeleteConfirmModal();
})
.catch((err) => {
this.setState({ errorMessageForDeleting: err.message });
});
}
showDeleteConfirmModal() {
this.setState({ isDeleteConfirmModalShown: true });
}
closeDeleteConfirmModal() {
this.setState({
commentToDelete: undefined,
isDeleteConfirmModalShown: false,
errorMessageForDeleting: undefined,
});
}
replyButtonClickedHandler(commentId) {
const ids = this.state.showEditorIds.add(commentId);
this.setState({ showEditorIds: ids });
}
// adds replies to specific comment object
addRepliesToComments(comments, replies) {
const commentsWithReplies = [];
const commentsCopy = comments.slice();
commentsCopy.forEach((comment) => {
comment.replyList = [];
replies.forEach((reply) => {
if (reply.replyTo === comment._id) {
comment.replyList.push(reply);
}
});
commentsWithReplies.push(comment);
});
return commentsWithReplies;
}
// returns replies
renderReplies(comment) {
return comment.replyList.map((reply) => {
return (
{ this.replyButtonClickedHandler(reply._id) }}
crowi={this.props.crowi}
replyTo={comment._id}
/>
);
});
}
/**
* generate Elements of Comment
*
* @param {any} comments Array of Comment Model Obj
*
* @memberOf PageComments
*/
generateCommentElements(comments, replies) {
const commentsWithReplies = this.addRepliesToComments(comments, replies);
return commentsWithReplies.map((comment) => {
const commentId = comment._id;
const showEditor = this.state.showEditorIds.has(commentId);
return (
{ this.replyButtonClickedHandler(commentId) }}
crowi={this.props.crowi}
replyTo={undefined}
/>
{this.renderReplies(comment)}
{ showEditor && (
)}
);
});
}
render() {
const currentComments = [];
const newerComments = [];
const olderComments = [];
const currentReplies = [];
const newerReplies = [];
const olderReplies = [];
let comments = this.props.commentContainer.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 = (
);
const newerBlock = (
);
const olderBlock = (
);
// 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}
);
}
}
/**
* Wrapper component for using unstated
*/
class PageCommentsWrapper extends React.Component {
render() {
return (
{ commentContainer => (
// eslint-disable-next-line arrow-body-style
)}
);
}
}
PageCommentsWrapper.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,
};
PageComments.propTypes = {
commentContainer: PropTypes.object.isRequired,
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 })(PageCommentsWrapper);