import * as classnames from "classnames"; import * as moment from "moment"; import * as React from "react"; import Icon from "../application/icon.component"; import AddCommentForm from "./add_comment_form.component"; import DownVoteButton from "./down_vote_button.component"; import UpVoteButton from "./up_vote_button.component"; import { AddCommentFormSessionFragment, CommentFragment, } from "../support/schema"; const { I18n } = require("react-i18nify"); interface CommentProps { comment: CommentFragment; session: AddCommentFormSessionFragment & { user: any; } | null; articleClassName?: string; isRootComment?: boolean; votable?: boolean; } interface CommentState { showReplyForm: boolean; } /** * A single comment component with the author info and the comment's body * @class * @augments Component */ class Comment extends React.Component { public static defaultProps: any = { articleClassName: "comment", isRootComment: false, session: null, votable: false, }; public commentNode: HTMLElement; constructor(props: CommentProps) { super(props); this.state = { showReplyForm: false, }; } public componentDidMount() { const { comment: { id } } = this.props; const hash = document.location.hash; const regex = new RegExp(`#comment_${id}`); function scrollTo(element: Element, to: number, duration: number) { if (duration <= 0) { return; } const difference = to - element.scrollTop; const perTick = difference / duration * 10; setTimeout(() => { element.scrollTop = element.scrollTop + perTick; if (element.scrollTop === to) { return; } scrollTo(element, to, duration - 10); }, 10); } if (regex.test(hash)) { scrollTo(document.body, this.commentNode.offsetTop, 200); } if (window.$(document).foundation) { window.$(`#flagModalComment${id}`).foundation(); } } public getNodeReference = (commentNode: HTMLElement) => this.commentNode = commentNode; public render(): JSX.Element { const { session, comment: { id, author, body, createdAt }, articleClassName } = this.props; const formattedCreatedAt = ` ${moment(createdAt).format("LLL")}`; let modalName = "loginModal"; if (session && session.user) { modalName = `flagModalComment${id}`; } return (
author-avatar { author.deleted ? {I18n.t("components.comment.deleted_user")} : {author.name} } { !author.isUser && author.isVerified &&   {I18n.t("components.comment.verified_user_group")} }
{this._renderFlagModal()}

{this._renderAlignmentBadge()} {body}

{this._renderReplyButton()} {this._renderVoteButtons()}
{this._renderReplies()} {this._renderAdditionalReplyButton()} {this._renderReplyForm()}
); } private toggleReplyForm = () => { const { showReplyForm } = this.state; this.setState({ showReplyForm: !showReplyForm }); } /** * Render reply button if user can reply the comment * @private * @returns {Void|DOMElement} - Render the reply button or not if user can reply */ private _renderReplyButton() { const { comment: { acceptsNewComments }, session } = this.props; if (session && acceptsNewComments) { return ( ); } return  ; } /** * Render additional reply button if user can reply the comment at the bottom of a conversation * @private * @returns {Void|DOMElement} - Render the reply button or not if user can reply */ private _renderAdditionalReplyButton() { const { comment: { acceptsNewComments, hasComments }, session, isRootComment } = this.props; if (session && acceptsNewComments) { if (hasComments && isRootComment) { return (
); } } return null; } /** * Render upVote and downVote buttons when the comment is votable * @private * @returns {Void|DOMElement} - Render the upVote and downVote buttons or not */ private _renderVoteButtons() { const { comment, votable } = this.props; if (votable) { return (
); } return  ; } /** * Render comment's comments alternating the css class * @private * @returns {Void|DomElement} - A wrapper element with comment's comments inside */ private _renderReplies() { const { comment: { id, hasComments, comments }, session, votable, articleClassName } = this.props; let replyArticleClassName = "comment comment--nested"; if (articleClassName === "comment comment--nested") { replyArticleClassName = `${replyArticleClassName} comment--nested--alt`; } if (hasComments) { return (
{ comments.map((reply: CommentFragment) => ( )) }
); } return null; } /** * Render reply form based on the current component state * @private * @returns {Void|ReactElement} - Render the AddCommentForm component or not */ private _renderReplyForm() { const { session, comment } = this.props; const { showReplyForm } = this.state; if (session && showReplyForm) { return ( ); } return null; } /** * Render alignment badge if comment's alignment is 0 or -1 * @private * @returns {Void|DOMElement} - The alignment's badge or not */ private _renderAlignmentBadge() { const { comment: { alignment } } = this.props; const spanClassName = classnames("label alignment", { success: alignment === 1, alert: alignment === -1, }); let label = ""; if (alignment === 1) { label = I18n.t("components.comment.alignment.in_favor"); } else { label = I18n.t("components.comment.alignment.against"); } if (alignment === 1 || alignment === -1) { return ( {label}   ); } return null; } /** * Render a modal to report the comment. * @private * @return {Void|DOMElement} - The comment's report modal or not. */ private _renderFlagModal() { const { session, comment: { id, sgid, alreadyReported } } = this.props; const authenticityToken = this._getAuthenticityToken(); const closeModal = () => { window.$(`#flagModalComment${id}`).foundation("close"); }; if (session && session.user) { return (

{I18n.t("components.comment.report.title")}

{ (() => { if (alreadyReported) { return (

{I18n.t("components.comment.report.already_reported")}

); } return [

{I18n.t("components.comment.report.description")}

, (