import { Component, linkEvent } from 'inferno'; import { Link } from 'inferno-router'; import { CommentNode as CommentNodeI, CommentLikeForm, CommentForm as CommentFormI, EditUserMentionForm, SaveCommentForm, BanFromCommunityForm, BanUserForm, CommunityUser, UserView, AddModToCommunityForm, AddAdminForm, TransferCommunityForm, TransferSiteForm, BanType, } from '../interfaces'; import { WebSocketService, UserService } from '../services'; import { mdToHtml, getUnixTime, canMod, isMod, pictshareAvatarThumbnail, } from '../utils'; import * as moment from 'moment'; import { MomentTime } from './moment-time'; import { CommentForm } from './comment-form'; import { CommentNodes } from './comment-nodes'; import { i18n } from '../i18next'; import { T } from 'inferno-i18next'; interface CommentNodeState { showReply: boolean; showEdit: boolean; showRemoveDialog: boolean; removeReason: string; showBanDialog: boolean; banReason: string; banExpires: string; banType: BanType; showConfirmTransferSite: boolean; showConfirmTransferCommunity: boolean; collapsed: boolean; viewSource: boolean; } interface CommentNodeProps { node: CommentNodeI; noIndent?: boolean; viewOnly?: boolean; locked?: boolean; markable?: boolean; moderators: Array; admins: Array; postCreatorId?: number; } export class CommentNode extends Component { private emptyState: CommentNodeState = { showReply: false, showEdit: false, showRemoveDialog: false, removeReason: null, showBanDialog: false, banReason: null, banExpires: null, banType: BanType.Community, collapsed: false, viewSource: false, showConfirmTransferSite: false, showConfirmTransferCommunity: false, }; constructor(props: any, context: any) { super(props, context); this.state = this.emptyState; this.handleReplyCancel = this.handleReplyCancel.bind(this); this.handleCommentLike = this.handleCommentLike.bind(this); this.handleCommentDisLike = this.handleCommentDisLike.bind(this); } render() { let node = this.props.node; return (
{!this.state.collapsed && (
{node.comment.score}
{WebSocketService.Instance.site.enable_downvotes && ( )}
)}
  • {node.comment.creator_avatar && ( )} {node.comment.creator_name}
  • {this.isMod && (
  • #
  • )} {this.isAdmin && (
  • #
  • )} {this.isPostCreator && (
  • #
  • )} {(node.comment.banned_from_community || node.comment.banned) && (
  • #
  • )}
  • (+{node.comment.upvotes} | -{node.comment.downvotes} )
  • {this.state.collapsed ? '[+]' : '[-]'}
{this.state.showEdit && ( )} {!this.state.showEdit && !this.state.collapsed && (
{this.state.viewSource ? (
{this.commentUnlessRemoved}
) : (
)}
    {UserService.Instance.user && !this.props.viewOnly && ( <>
  • #
  • {node.comment.saved ? i18n.t('unsave') : i18n.t('save')}
  • {this.myComment && ( <>
  • #
  • {!node.comment.deleted ? i18n.t('delete') : i18n.t('restore')}
  • )} {/* Admins and mods can remove comments */} {(this.canMod || this.canAdmin) && (
  • {!node.comment.removed ? ( # ) : ( # )}
  • )} {/* Mods can ban from community, and appoint as mods to community */} {this.canMod && ( <> {!this.isMod && (
  • {!node.comment.banned_from_community ? ( # ) : ( # )}
  • )} {!node.comment.banned_from_community && (
  • {this.isMod ? i18n.t('remove_as_mod') : i18n.t('appoint_as_mod')}
  • )} )} {/* Community creators and admins can transfer community to another mod */} {(this.amCommunityCreator || this.canAdmin) && this.isMod && (
  • {!this.state.showConfirmTransferCommunity ? ( # ) : ( <> # # # )}
  • )} {/* Admins can ban from all, and appoint other admins */} {this.canAdmin && ( <> {!this.isAdmin && (
  • {!node.comment.banned ? ( # ) : ( # )}
  • )} {!node.comment.banned && (
  • {this.isAdmin ? i18n.t('remove_as_admin') : i18n.t('appoint_as_admin')}
  • )} )} {/* Site Creator can transfer to another admin */} {this.amSiteCreator && this.isAdmin && (
  • {!this.state.showConfirmTransferSite ? ( # ) : ( <> # # # )}
  • )} )}
  • #
  • #
  • {this.props.markable && (
  • {node.comment.read ? i18n.t('mark_as_unread') : i18n.t('mark_as_read')}
  • )}
)}
{this.state.showRemoveDialog && (
)} {this.state.showBanDialog && (
{/* TODO hold off on expires until later */} {/*
*/} {/* */} {/* */} {/*
*/}
)} {this.state.showReply && ( )} {node.children && !this.state.collapsed && ( )} {/* A collapsed clearfix */} {this.state.collapsed &&
}
); } get myComment(): boolean { return ( UserService.Instance.user && this.props.node.comment.creator_id == UserService.Instance.user.id ); } get isMod(): boolean { return ( this.props.moderators && isMod( this.props.moderators.map(m => m.user_id), this.props.node.comment.creator_id ) ); } get isAdmin(): boolean { return ( this.props.admins && isMod( this.props.admins.map(a => a.id), this.props.node.comment.creator_id ) ); } get isPostCreator(): boolean { return this.props.node.comment.creator_id == this.props.postCreatorId; } get canMod(): boolean { if (this.props.admins && this.props.moderators) { let adminsThenMods = this.props.admins .map(a => a.id) .concat(this.props.moderators.map(m => m.user_id)); return canMod( UserService.Instance.user, adminsThenMods, this.props.node.comment.creator_id ); } else { return false; } } get canAdmin(): boolean { return ( this.props.admins && canMod( UserService.Instance.user, this.props.admins.map(a => a.id), this.props.node.comment.creator_id ) ); } get amCommunityCreator(): boolean { return ( this.props.moderators && UserService.Instance.user && this.props.node.comment.creator_id != UserService.Instance.user.id && UserService.Instance.user.id == this.props.moderators[0].user_id ); } get amSiteCreator(): boolean { return ( this.props.admins && UserService.Instance.user && this.props.node.comment.creator_id != UserService.Instance.user.id && UserService.Instance.user.id == this.props.admins[0].id ); } get commentUnlessRemoved(): string { let node = this.props.node; return node.comment.removed ? `*${i18n.t('removed')}*` : node.comment.deleted ? `*${i18n.t('deleted')}*` : node.comment.content; } handleReplyClick(i: CommentNode) { i.state.showReply = true; i.setState(i.state); } handleEditClick(i: CommentNode) { i.state.showEdit = true; i.setState(i.state); } handleDeleteClick(i: CommentNode) { let deleteForm: CommentFormI = { content: i.props.node.comment.content, edit_id: i.props.node.comment.id, creator_id: i.props.node.comment.creator_id, post_id: i.props.node.comment.post_id, parent_id: i.props.node.comment.parent_id, deleted: !i.props.node.comment.deleted, auth: null, }; WebSocketService.Instance.editComment(deleteForm); } handleSaveCommentClick(i: CommentNode) { let saved = i.props.node.comment.saved == undefined ? true : !i.props.node.comment.saved; let form: SaveCommentForm = { comment_id: i.props.node.comment.id, save: saved, }; WebSocketService.Instance.saveComment(form); } handleReplyCancel() { this.state.showReply = false; this.state.showEdit = false; this.setState(this.state); } handleCommentLike(i: CommentNodeI) { let form: CommentLikeForm = { comment_id: i.comment.id, post_id: i.comment.post_id, score: i.comment.my_vote == 1 ? 0 : 1, }; WebSocketService.Instance.likeComment(form); } handleCommentDisLike(i: CommentNodeI) { let form: CommentLikeForm = { comment_id: i.comment.id, post_id: i.comment.post_id, score: i.comment.my_vote == -1 ? 0 : -1, }; WebSocketService.Instance.likeComment(form); } handleModRemoveShow(i: CommentNode) { i.state.showRemoveDialog = true; i.setState(i.state); } handleModRemoveReasonChange(i: CommentNode, event: any) { i.state.removeReason = event.target.value; i.setState(i.state); } handleModRemoveSubmit(i: CommentNode) { event.preventDefault(); let form: CommentFormI = { content: i.props.node.comment.content, edit_id: i.props.node.comment.id, creator_id: i.props.node.comment.creator_id, post_id: i.props.node.comment.post_id, parent_id: i.props.node.comment.parent_id, removed: !i.props.node.comment.removed, reason: i.state.removeReason, auth: null, }; WebSocketService.Instance.editComment(form); i.state.showRemoveDialog = false; i.setState(i.state); } handleMarkRead(i: CommentNode) { // if it has a user_mention_id field, then its a mention if (i.props.node.comment.user_mention_id) { let form: EditUserMentionForm = { user_mention_id: i.props.node.comment.user_mention_id, read: !i.props.node.comment.read, }; WebSocketService.Instance.editUserMention(form); } else { let form: CommentFormI = { content: i.props.node.comment.content, edit_id: i.props.node.comment.id, creator_id: i.props.node.comment.creator_id, post_id: i.props.node.comment.post_id, parent_id: i.props.node.comment.parent_id, read: !i.props.node.comment.read, auth: null, }; WebSocketService.Instance.editComment(form); } } handleModBanFromCommunityShow(i: CommentNode) { i.state.showBanDialog = true; i.state.banType = BanType.Community; i.setState(i.state); } handleModBanShow(i: CommentNode) { i.state.showBanDialog = true; i.state.banType = BanType.Site; i.setState(i.state); } handleModBanReasonChange(i: CommentNode, event: any) { i.state.banReason = event.target.value; i.setState(i.state); } handleModBanExpiresChange(i: CommentNode, event: any) { i.state.banExpires = event.target.value; i.setState(i.state); } handleModBanFromCommunitySubmit(i: CommentNode) { i.state.banType = BanType.Community; i.setState(i.state); i.handleModBanBothSubmit(i); } handleModBanSubmit(i: CommentNode) { i.state.banType = BanType.Site; i.setState(i.state); i.handleModBanBothSubmit(i); } handleModBanBothSubmit(i: CommentNode) { event.preventDefault(); if (i.state.banType == BanType.Community) { let form: BanFromCommunityForm = { user_id: i.props.node.comment.creator_id, community_id: i.props.node.comment.community_id, ban: !i.props.node.comment.banned_from_community, reason: i.state.banReason, expires: getUnixTime(i.state.banExpires), }; WebSocketService.Instance.banFromCommunity(form); } else { let form: BanUserForm = { user_id: i.props.node.comment.creator_id, ban: !i.props.node.comment.banned, reason: i.state.banReason, expires: getUnixTime(i.state.banExpires), }; WebSocketService.Instance.banUser(form); } i.state.showBanDialog = false; i.setState(i.state); } handleAddModToCommunity(i: CommentNode) { let form: AddModToCommunityForm = { user_id: i.props.node.comment.creator_id, community_id: i.props.node.comment.community_id, added: !i.isMod, }; WebSocketService.Instance.addModToCommunity(form); i.setState(i.state); } handleAddAdmin(i: CommentNode) { let form: AddAdminForm = { user_id: i.props.node.comment.creator_id, added: !i.isAdmin, }; WebSocketService.Instance.addAdmin(form); i.setState(i.state); } handleShowConfirmTransferCommunity(i: CommentNode) { i.state.showConfirmTransferCommunity = true; i.setState(i.state); } handleCancelShowConfirmTransferCommunity(i: CommentNode) { i.state.showConfirmTransferCommunity = false; i.setState(i.state); } handleTransferCommunity(i: CommentNode) { let form: TransferCommunityForm = { community_id: i.props.node.comment.community_id, user_id: i.props.node.comment.creator_id, }; WebSocketService.Instance.transferCommunity(form); i.state.showConfirmTransferCommunity = false; i.setState(i.state); } handleShowConfirmTransferSite(i: CommentNode) { i.state.showConfirmTransferSite = true; i.setState(i.state); } handleCancelShowConfirmTransferSite(i: CommentNode) { i.state.showConfirmTransferSite = false; i.setState(i.state); } handleTransferSite(i: CommentNode) { let form: TransferSiteForm = { user_id: i.props.node.comment.creator_id, }; WebSocketService.Instance.transferSite(form); i.state.showConfirmTransferSite = false; i.setState(i.state); } get isCommentNew(): boolean { let now = moment.utc().subtract(10, 'minutes'); let then = moment.utc(this.props.node.comment.published); return now.isBefore(then); } handleCommentCollapse(i: CommentNode) { i.state.collapsed = !i.state.collapsed; i.setState(i.state); } handleViewSource(i: CommentNode) { i.state.viewSource = !i.state.viewSource; i.setState(i.state); } }