1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
10 CommunityModeratorView,
15 MarkPersonMentionAsRead,
22 } from "lemmy-js-client";
23 import moment from "moment";
24 import { i18n } from "../../i18next";
25 import { BanType, CommentNode as CommentNodeI } from "../../interfaces";
26 import { UserService, WebSocketService } from "../../services";
39 import { Icon, Spinner } from "../common/icon";
40 import { MomentTime } from "../common/moment-time";
41 import { CommunityLink } from "../community/community-link";
42 import { PersonListing } from "../person/person-listing";
43 import { CommentForm } from "./comment-form";
44 import { CommentNodes } from "./comment-nodes";
46 interface CommentNodeState {
49 showRemoveDialog: boolean;
51 showBanDialog: boolean;
56 showConfirmTransferSite: boolean;
57 showConfirmTransferCommunity: boolean;
58 showConfirmAppointAsMod: boolean;
59 showConfirmAppointAsAdmin: boolean;
62 showAdvanced: boolean;
63 showReportDialog: boolean;
74 interface CommentNodeProps {
81 showContext?: boolean;
82 moderators: CommunityModeratorView[];
83 admins: PersonViewSafe[];
84 // TODO is this necessary, can't I get it from the node itself?
85 postCreatorId?: number;
86 showCommunity?: boolean;
87 enableDownvotes: boolean;
90 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
91 private emptyState: CommentNodeState = {
94 showRemoveDialog: false,
100 banType: BanType.Community,
104 showConfirmTransferSite: false,
105 showConfirmTransferCommunity: false,
106 showConfirmAppointAsMod: false,
107 showConfirmAppointAsAdmin: false,
108 showReportDialog: false,
110 my_vote: this.props.node.comment_view.my_vote,
111 score: this.props.node.comment_view.counts.score,
112 upvotes: this.props.node.comment_view.counts.upvotes,
113 downvotes: this.props.node.comment_view.counts.downvotes,
114 borderColor: this.props.node.depth
115 ? colorList[this.props.node.depth % colorList.length]
121 constructor(props: any, context: any) {
122 super(props, context);
124 this.state = this.emptyState;
125 this.handleReplyCancel = this.handleReplyCancel.bind(this);
126 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
127 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
130 // TODO see if there's a better way to do this, and all willReceiveProps
131 componentWillReceiveProps(nextProps: CommentNodeProps) {
132 let cv = nextProps.node.comment_view;
133 this.state.my_vote = cv.my_vote;
134 this.state.upvotes = cv.counts.upvotes;
135 this.state.downvotes = cv.counts.downvotes;
136 this.state.score = cv.counts.score;
137 this.state.readLoading = false;
138 this.state.saveLoading = false;
139 this.setState(this.state);
143 let node = this.props.node;
144 let cv = this.props.node.comment_view;
147 className={`comment ${
148 cv.comment.parent_id && !this.props.noIndent ? "ml-1" : ""
152 id={`comment-${cv.comment.id}`}
153 className={`details comment-node py-2 ${
154 !this.props.noBorder ? "border-top border-light" : ""
155 } ${this.isCommentNew ? "mark" : ""}`}
157 !this.props.noIndent &&
158 cv.comment.parent_id &&
159 `border-left: 2px ${this.state.borderColor} solid !important`
163 class={`${!this.props.noIndent && cv.comment.parent_id && "ml-2"}`}
165 <div class="d-flex flex-wrap align-items-center text-muted small">
167 <PersonListing person={cv.creator} />
171 <div className="badge badge-light d-none d-sm-inline mr-2">
176 <div className="badge badge-light d-none d-sm-inline mr-2">
180 {this.isPostCreator && (
181 <div className="badge badge-light d-none d-sm-inline mr-2">
185 {(cv.creator_banned_from_community || cv.creator.banned) && (
186 <div className="badge badge-danger mr-2">
190 {this.props.showCommunity && (
192 <span class="mx-1">{i18n.t("to")}</span>
193 <CommunityLink community={cv.community} />
194 <span class="mx-2">•</span>
195 <Link className="mr-2" to={`/post/${cv.post.id}`}>
201 class="btn btn-sm text-muted"
202 onClick={linkEvent(this, this.handleCommentCollapse)}
203 aria-label={this.expandText}
204 data-tippy-content={this.expandText}
206 {this.state.collapsed ? (
207 <Icon icon="plus-square" classes="icon-inline" />
209 <Icon icon="minus-square" classes="icon-inline" />
213 {/* This is an expanding spacer for mobile */}
214 <div className="mr-lg-5 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
218 className={`unselectable pointer ${this.scoreColor}`}
219 onClick={linkEvent(node, this.handleCommentUpvote)}
220 data-tippy-content={this.pointsTippy}
223 class="mr-1 font-weight-bold"
224 aria-label={i18n.t("number_of_points", {
225 count: this.state.score,
226 formattedCount: this.state.score,
229 {numToSI(this.state.score)}
232 <span className="mr-1">•</span>
236 <MomentTime data={cv.comment} />
239 {/* end of user row */}
240 {this.state.showEdit && (
244 onReplyCancel={this.handleReplyCancel}
245 disabled={this.props.locked}
249 {!this.state.showEdit && !this.state.collapsed && (
251 {this.state.viewSource ? (
252 <pre>{this.commentUnlessRemoved}</pre>
256 dangerouslySetInnerHTML={mdToHtml(
257 this.commentUnlessRemoved
261 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
262 {this.props.showContext && this.linkBtn()}
263 {this.props.markable && (
265 class="btn btn-link btn-animate text-muted"
266 onClick={linkEvent(this, this.handleMarkRead)}
268 this.commentOrMentionRead
269 ? i18n.t("mark_as_unread")
270 : i18n.t("mark_as_read")
273 this.commentOrMentionRead
274 ? i18n.t("mark_as_unread")
275 : i18n.t("mark_as_read")
278 {this.state.readLoading ? (
283 classes={`icon-inline ${
284 this.commentOrMentionRead && "text-success"
290 {UserService.Instance.myUserInfo && !this.props.viewOnly && (
293 className={`btn btn-link btn-animate ${
294 this.state.my_vote == 1 ? "text-info" : "text-muted"
296 onClick={linkEvent(node, this.handleCommentUpvote)}
297 data-tippy-content={i18n.t("upvote")}
298 aria-label={i18n.t("upvote")}
300 <Icon icon="arrow-up1" classes="icon-inline" />
302 this.state.upvotes !== this.state.score && (
304 {numToSI(this.state.upvotes)}
308 {this.props.enableDownvotes && (
310 className={`btn btn-link btn-animate ${
311 this.state.my_vote == -1
315 onClick={linkEvent(node, this.handleCommentDownvote)}
316 data-tippy-content={i18n.t("downvote")}
317 aria-label={i18n.t("downvote")}
319 <Icon icon="arrow-down1" classes="icon-inline" />
321 this.state.upvotes !== this.state.score && (
323 {numToSI(this.state.downvotes)}
329 class="btn btn-link btn-animate text-muted"
330 onClick={linkEvent(this, this.handleReplyClick)}
331 data-tippy-content={i18n.t("reply")}
332 aria-label={i18n.t("reply")}
334 <Icon icon="reply1" classes="icon-inline" />
336 {!this.state.showAdvanced ? (
338 className="btn btn-link btn-animate text-muted"
339 onClick={linkEvent(this, this.handleShowAdvanced)}
340 data-tippy-content={i18n.t("more")}
341 aria-label={i18n.t("more")}
343 <Icon icon="more-vertical" classes="icon-inline" />
347 {!this.myComment && (
349 <button class="btn btn-link btn-animate">
351 className="text-muted"
352 to={`/create_private_message/recipient/${cv.creator.id}`}
353 title={i18n.t("message").toLowerCase()}
359 class="btn btn-link btn-animate text-muted"
362 this.handleShowReportDialog
364 data-tippy-content={i18n.t(
367 aria-label={i18n.t("show_report_dialog")}
372 class="btn btn-link btn-animate text-muted"
375 this.handleBlockUserClick
377 data-tippy-content={i18n.t("block_user")}
378 aria-label={i18n.t("block_user")}
380 <Icon icon="slash" />
385 class="btn btn-link btn-animate text-muted"
388 this.handleSaveCommentClick
391 cv.saved ? i18n.t("unsave") : i18n.t("save")
394 cv.saved ? i18n.t("unsave") : i18n.t("save")
397 {this.state.saveLoading ? (
402 classes={`icon-inline ${
403 cv.saved && "text-warning"
409 className="btn btn-link btn-animate text-muted"
410 onClick={linkEvent(this, this.handleViewSource)}
411 data-tippy-content={i18n.t("view_source")}
412 aria-label={i18n.t("view_source")}
416 classes={`icon-inline ${
417 this.state.viewSource && "text-success"
424 class="btn btn-link btn-animate text-muted"
425 onClick={linkEvent(this, this.handleEditClick)}
426 data-tippy-content={i18n.t("edit")}
427 aria-label={i18n.t("edit")}
429 <Icon icon="edit" classes="icon-inline" />
432 class="btn btn-link btn-animate text-muted"
435 this.handleDeleteClick
450 classes={`icon-inline ${
451 cv.comment.deleted && "text-danger"
457 {/* Admins and mods can remove comments */}
458 {(this.canMod || this.canAdmin) && (
460 {!cv.comment.removed ? (
462 class="btn btn-link btn-animate text-muted"
465 this.handleModRemoveShow
467 aria-label={i18n.t("remove")}
473 class="btn btn-link btn-animate text-muted"
476 this.handleModRemoveSubmit
478 aria-label={i18n.t("restore")}
485 {/* Mods can ban from community, and appoint as mods to community */}
489 (!cv.creator_banned_from_community ? (
491 class="btn btn-link btn-animate text-muted"
494 this.handleModBanFromCommunityShow
496 aria-label={i18n.t("ban")}
502 class="btn btn-link btn-animate text-muted"
505 this.handleModBanFromCommunitySubmit
507 aria-label={i18n.t("unban")}
512 {!cv.creator_banned_from_community &&
513 (!this.state.showConfirmAppointAsMod ? (
515 class="btn btn-link btn-animate text-muted"
518 this.handleShowConfirmAppointAsMod
522 ? i18n.t("remove_as_mod")
523 : i18n.t("appoint_as_mod")
527 ? i18n.t("remove_as_mod")
528 : i18n.t("appoint_as_mod")}
533 class="btn btn-link btn-animate text-muted"
534 aria-label={i18n.t("are_you_sure")}
536 {i18n.t("are_you_sure")}
539 class="btn btn-link btn-animate text-muted"
542 this.handleAddModToCommunity
544 aria-label={i18n.t("yes")}
549 class="btn btn-link btn-animate text-muted"
552 this.handleCancelConfirmAppointAsMod
554 aria-label={i18n.t("no")}
562 {/* Community creators and admins can transfer community to another mod */}
563 {(this.amCommunityCreator || this.canAdmin) &&
566 (!this.state.showConfirmTransferCommunity ? (
568 class="btn btn-link btn-animate text-muted"
571 this.handleShowConfirmTransferCommunity
573 aria-label={i18n.t("transfer_community")}
575 {i18n.t("transfer_community")}
580 class="btn btn-link btn-animate text-muted"
581 aria-label={i18n.t("are_you_sure")}
583 {i18n.t("are_you_sure")}
586 class="btn btn-link btn-animate text-muted"
589 this.handleTransferCommunity
591 aria-label={i18n.t("yes")}
596 class="btn btn-link btn-animate text-muted"
600 .handleCancelShowConfirmTransferCommunity
602 aria-label={i18n.t("no")}
608 {/* Admins can ban from all, and appoint other admins */}
612 (!cv.creator.banned ? (
614 class="btn btn-link btn-animate text-muted"
617 this.handleModBanShow
619 aria-label={i18n.t("ban_from_site")}
621 {i18n.t("ban_from_site")}
625 class="btn btn-link btn-animate text-muted"
628 this.handleModBanSubmit
630 aria-label={i18n.t("unban_from_site")}
632 {i18n.t("unban_from_site")}
635 {!cv.creator.banned &&
637 (!this.state.showConfirmAppointAsAdmin ? (
639 class="btn btn-link btn-animate text-muted"
642 this.handleShowConfirmAppointAsAdmin
646 ? i18n.t("remove_as_admin")
647 : i18n.t("appoint_as_admin")
651 ? i18n.t("remove_as_admin")
652 : i18n.t("appoint_as_admin")}
656 <button class="btn btn-link btn-animate text-muted">
657 {i18n.t("are_you_sure")}
660 class="btn btn-link btn-animate text-muted"
665 aria-label={i18n.t("yes")}
670 class="btn btn-link btn-animate text-muted"
673 this.handleCancelConfirmAppointAsAdmin
675 aria-label={i18n.t("no")}
683 {/* Site Creator can transfer to another admin */}
684 {this.amSiteCreator &&
687 (!this.state.showConfirmTransferSite ? (
689 class="btn btn-link btn-animate text-muted"
692 this.handleShowConfirmTransferSite
694 aria-label={i18n.t("transfer_site")}
696 {i18n.t("transfer_site")}
701 class="btn btn-link btn-animate text-muted"
702 aria-label={i18n.t("are_you_sure")}
704 {i18n.t("are_you_sure")}
707 class="btn btn-link btn-animate text-muted"
710 this.handleTransferSite
712 aria-label={i18n.t("yes")}
717 class="btn btn-link btn-animate text-muted"
720 this.handleCancelShowConfirmTransferSite
722 aria-label={i18n.t("no")}
733 {/* end of button group */}
738 {/* end of details */}
739 {this.state.showRemoveDialog && (
742 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
746 htmlFor={`mod-remove-reason-${cv.comment.id}`}
752 id={`mod-remove-reason-${cv.comment.id}`}
753 class="form-control mr-2"
754 placeholder={i18n.t("reason")}
755 value={this.state.removeReason}
756 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
760 class="btn btn-secondary"
761 aria-label={i18n.t("remove_comment")}
763 {i18n.t("remove_comment")}
767 {this.state.showReportDialog && (
770 onSubmit={linkEvent(this, this.handleReportSubmit)}
772 <label class="sr-only" htmlFor={`report-reason-${cv.comment.id}`}>
778 id={`report-reason-${cv.comment.id}`}
779 class="form-control mr-2"
780 placeholder={i18n.t("reason")}
781 value={this.state.reportReason}
782 onInput={linkEvent(this, this.handleReportReasonChange)}
786 class="btn btn-secondary"
787 aria-label={i18n.t("create_report")}
789 {i18n.t("create_report")}
793 {this.state.showBanDialog && (
794 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
795 <div class="form-group row">
797 class="col-form-label"
798 htmlFor={`mod-ban-reason-${cv.comment.id}`}
804 id={`mod-ban-reason-${cv.comment.id}`}
805 class="form-control mr-2"
806 placeholder={i18n.t("reason")}
807 value={this.state.banReason}
808 onInput={linkEvent(this, this.handleModBanReasonChange)}
810 <div class="form-group">
811 <div class="form-check">
813 class="form-check-input"
814 id="mod-ban-remove-data"
816 checked={this.state.removeData}
817 onChange={linkEvent(this, this.handleModRemoveDataChange)}
820 class="form-check-label"
821 htmlFor="mod-ban-remove-data"
822 title={i18n.t("remove_content_more")}
824 {i18n.t("remove_content")}
829 {/* TODO hold off on expires until later */}
830 {/* <div class="form-group row"> */}
831 {/* <label class="col-form-label">Expires</label> */}
832 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
834 <div class="form-group row">
837 class="btn btn-secondary"
838 aria-label={i18n.t("ban")}
840 {i18n.t("ban")} {cv.creator.name}
845 {this.state.showReply && (
848 onReplyCancel={this.handleReplyCancel}
849 disabled={this.props.locked}
853 {node.children && !this.state.collapsed && (
855 nodes={node.children}
856 locked={this.props.locked}
857 moderators={this.props.moderators}
858 admins={this.props.admins}
859 postCreatorId={this.props.postCreatorId}
860 enableDownvotes={this.props.enableDownvotes}
863 {/* A collapsed clearfix */}
864 {this.state.collapsed && <div class="row col-12"></div>}
869 get commentOrMentionRead() {
870 let cv = this.props.node.comment_view;
871 return this.isPersonMentionType(cv)
872 ? cv.person_mention.read
876 linkBtn(small = false) {
877 let cv = this.props.node.comment_view;
880 className={`btn ${small && "btn-sm"} btn-link btn-animate text-muted`}
881 to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
882 title={this.props.showContext ? i18n.t("show_context") : i18n.t("link")}
884 <Icon icon="link" classes="icon-inline" />
893 get myComment(): boolean {
895 this.props.node.comment_view.creator.id ==
896 UserService.Instance.myUserInfo?.local_user_view.person.id
900 get isMod(): boolean {
902 this.props.moderators &&
904 this.props.moderators.map(m => m.moderator.id),
905 this.props.node.comment_view.creator.id
910 get isAdmin(): boolean {
914 this.props.admins.map(a => a.person.id),
915 this.props.node.comment_view.creator.id
920 get isPostCreator(): boolean {
921 return this.props.node.comment_view.creator.id == this.props.postCreatorId;
924 get canMod(): boolean {
925 if (this.props.admins && this.props.moderators) {
926 let adminsThenMods = this.props.admins
927 .map(a => a.person.id)
928 .concat(this.props.moderators.map(m => m.moderator.id));
931 UserService.Instance.myUserInfo,
933 this.props.node.comment_view.creator.id
940 get canAdmin(): boolean {
944 UserService.Instance.myUserInfo,
945 this.props.admins.map(a => a.person.id),
946 this.props.node.comment_view.creator.id
951 get amCommunityCreator(): boolean {
953 this.props.moderators &&
954 UserService.Instance.myUserInfo &&
955 this.props.node.comment_view.creator.id !=
956 UserService.Instance.myUserInfo.local_user_view.person.id &&
957 UserService.Instance.myUserInfo.local_user_view.person.id ==
958 this.props.moderators[0].moderator.id
962 get amSiteCreator(): boolean {
965 UserService.Instance.myUserInfo &&
966 this.props.node.comment_view.creator.id !=
967 UserService.Instance.myUserInfo.local_user_view.person.id &&
968 UserService.Instance.myUserInfo.local_user_view.person.id ==
969 this.props.admins[0].person.id
973 get commentUnlessRemoved(): string {
974 let comment = this.props.node.comment_view.comment;
975 return comment.removed
976 ? `*${i18n.t("removed")}*`
978 ? `*${i18n.t("deleted")}*`
982 handleReplyClick(i: CommentNode) {
983 i.state.showReply = true;
987 handleEditClick(i: CommentNode) {
988 i.state.showEdit = true;
992 handleBlockUserClick(i: CommentNode) {
993 let blockUserForm: BlockPerson = {
994 person_id: i.props.node.comment_view.creator.id,
998 WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
1001 handleDeleteClick(i: CommentNode) {
1002 let comment = i.props.node.comment_view.comment;
1003 let deleteForm: DeleteComment = {
1004 comment_id: comment.id,
1005 deleted: !comment.deleted,
1008 WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
1011 handleSaveCommentClick(i: CommentNode) {
1012 let cv = i.props.node.comment_view;
1013 let save = cv.saved == undefined ? true : !cv.saved;
1014 let form: SaveComment = {
1015 comment_id: cv.comment.id,
1020 WebSocketService.Instance.send(wsClient.saveComment(form));
1022 i.state.saveLoading = true;
1023 i.setState(this.state);
1026 handleReplyCancel() {
1027 this.state.showReply = false;
1028 this.state.showEdit = false;
1029 this.setState(this.state);
1032 handleCommentUpvote(i: CommentNodeI, event: any) {
1033 event.preventDefault();
1034 let new_vote = this.state.my_vote == 1 ? 0 : 1;
1036 if (this.state.my_vote == 1) {
1038 this.state.upvotes--;
1039 } else if (this.state.my_vote == -1) {
1040 this.state.downvotes--;
1041 this.state.upvotes++;
1042 this.state.score += 2;
1044 this.state.upvotes++;
1048 this.state.my_vote = new_vote;
1050 let form: CreateCommentLike = {
1051 comment_id: i.comment_view.comment.id,
1052 score: this.state.my_vote,
1056 WebSocketService.Instance.send(wsClient.likeComment(form));
1057 this.setState(this.state);
1061 handleCommentDownvote(i: CommentNodeI, event: any) {
1062 event.preventDefault();
1063 let new_vote = this.state.my_vote == -1 ? 0 : -1;
1065 if (this.state.my_vote == 1) {
1066 this.state.score -= 2;
1067 this.state.upvotes--;
1068 this.state.downvotes++;
1069 } else if (this.state.my_vote == -1) {
1070 this.state.downvotes--;
1073 this.state.downvotes++;
1077 this.state.my_vote = new_vote;
1079 let form: CreateCommentLike = {
1080 comment_id: i.comment_view.comment.id,
1081 score: this.state.my_vote,
1085 WebSocketService.Instance.send(wsClient.likeComment(form));
1086 this.setState(this.state);
1090 handleShowReportDialog(i: CommentNode) {
1091 i.state.showReportDialog = !i.state.showReportDialog;
1092 i.setState(i.state);
1095 handleReportReasonChange(i: CommentNode, event: any) {
1096 i.state.reportReason = event.target.value;
1097 i.setState(i.state);
1100 handleReportSubmit(i: CommentNode) {
1101 let comment = i.props.node.comment_view.comment;
1102 let form: CreateCommentReport = {
1103 comment_id: comment.id,
1104 reason: i.state.reportReason,
1107 WebSocketService.Instance.send(wsClient.createCommentReport(form));
1109 i.state.showReportDialog = false;
1110 i.setState(i.state);
1113 handleModRemoveShow(i: CommentNode) {
1114 i.state.showRemoveDialog = !i.state.showRemoveDialog;
1115 i.state.showBanDialog = false;
1116 i.setState(i.state);
1119 handleModRemoveReasonChange(i: CommentNode, event: any) {
1120 i.state.removeReason = event.target.value;
1121 i.setState(i.state);
1124 handleModRemoveDataChange(i: CommentNode, event: any) {
1125 i.state.removeData = event.target.checked;
1126 i.setState(i.state);
1129 handleModRemoveSubmit(i: CommentNode) {
1130 let comment = i.props.node.comment_view.comment;
1131 let form: RemoveComment = {
1132 comment_id: comment.id,
1133 removed: !comment.removed,
1134 reason: i.state.removeReason,
1137 WebSocketService.Instance.send(wsClient.removeComment(form));
1139 i.state.showRemoveDialog = false;
1140 i.setState(i.state);
1143 isPersonMentionType(
1144 item: CommentView | PersonMentionView
1145 ): item is PersonMentionView {
1146 return (item as PersonMentionView).person_mention?.id !== undefined;
1149 handleMarkRead(i: CommentNode) {
1150 if (i.isPersonMentionType(i.props.node.comment_view)) {
1151 let form: MarkPersonMentionAsRead = {
1152 person_mention_id: i.props.node.comment_view.person_mention.id,
1153 read: !i.props.node.comment_view.person_mention.read,
1156 WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
1158 let form: MarkCommentAsRead = {
1159 comment_id: i.props.node.comment_view.comment.id,
1160 read: !i.props.node.comment_view.comment.read,
1163 WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
1166 i.state.readLoading = true;
1167 i.setState(this.state);
1170 handleModBanFromCommunityShow(i: CommentNode) {
1171 i.state.showBanDialog = true;
1172 i.state.banType = BanType.Community;
1173 i.state.showRemoveDialog = false;
1174 i.setState(i.state);
1177 handleModBanShow(i: CommentNode) {
1178 i.state.showBanDialog = true;
1179 i.state.banType = BanType.Site;
1180 i.state.showRemoveDialog = false;
1181 i.setState(i.state);
1184 handleModBanReasonChange(i: CommentNode, event: any) {
1185 i.state.banReason = event.target.value;
1186 i.setState(i.state);
1189 handleModBanExpiresChange(i: CommentNode, event: any) {
1190 i.state.banExpires = event.target.value;
1191 i.setState(i.state);
1194 handleModBanFromCommunitySubmit(i: CommentNode) {
1195 i.state.banType = BanType.Community;
1196 i.setState(i.state);
1197 i.handleModBanBothSubmit(i);
1200 handleModBanSubmit(i: CommentNode) {
1201 i.state.banType = BanType.Site;
1202 i.setState(i.state);
1203 i.handleModBanBothSubmit(i);
1206 handleModBanBothSubmit(i: CommentNode) {
1207 let cv = i.props.node.comment_view;
1209 if (i.state.banType == BanType.Community) {
1210 // If its an unban, restore all their data
1211 let ban = !cv.creator_banned_from_community;
1213 i.state.removeData = false;
1215 let form: BanFromCommunity = {
1216 person_id: cv.creator.id,
1217 community_id: cv.community.id,
1219 remove_data: i.state.removeData,
1220 reason: i.state.banReason,
1221 expires: getUnixTime(i.state.banExpires),
1224 WebSocketService.Instance.send(wsClient.banFromCommunity(form));
1226 // If its an unban, restore all their data
1227 let ban = !cv.creator.banned;
1229 i.state.removeData = false;
1231 let form: BanPerson = {
1232 person_id: cv.creator.id,
1234 remove_data: i.state.removeData,
1235 reason: i.state.banReason,
1236 expires: getUnixTime(i.state.banExpires),
1239 WebSocketService.Instance.send(wsClient.banPerson(form));
1242 i.state.showBanDialog = false;
1243 i.setState(i.state);
1246 handleShowConfirmAppointAsMod(i: CommentNode) {
1247 i.state.showConfirmAppointAsMod = true;
1248 i.setState(i.state);
1251 handleCancelConfirmAppointAsMod(i: CommentNode) {
1252 i.state.showConfirmAppointAsMod = false;
1253 i.setState(i.state);
1256 handleAddModToCommunity(i: CommentNode) {
1257 let cv = i.props.node.comment_view;
1258 let form: AddModToCommunity = {
1259 person_id: cv.creator.id,
1260 community_id: cv.community.id,
1264 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
1265 i.state.showConfirmAppointAsMod = false;
1266 i.setState(i.state);
1269 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1270 i.state.showConfirmAppointAsAdmin = true;
1271 i.setState(i.state);
1274 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1275 i.state.showConfirmAppointAsAdmin = false;
1276 i.setState(i.state);
1279 handleAddAdmin(i: CommentNode) {
1280 let form: AddAdmin = {
1281 person_id: i.props.node.comment_view.creator.id,
1285 WebSocketService.Instance.send(wsClient.addAdmin(form));
1286 i.state.showConfirmAppointAsAdmin = false;
1287 i.setState(i.state);
1290 handleShowConfirmTransferCommunity(i: CommentNode) {
1291 i.state.showConfirmTransferCommunity = true;
1292 i.setState(i.state);
1295 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1296 i.state.showConfirmTransferCommunity = false;
1297 i.setState(i.state);
1300 handleTransferCommunity(i: CommentNode) {
1301 let cv = i.props.node.comment_view;
1302 let form: TransferCommunity = {
1303 community_id: cv.community.id,
1304 person_id: cv.creator.id,
1307 WebSocketService.Instance.send(wsClient.transferCommunity(form));
1308 i.state.showConfirmTransferCommunity = false;
1309 i.setState(i.state);
1312 handleShowConfirmTransferSite(i: CommentNode) {
1313 i.state.showConfirmTransferSite = true;
1314 i.setState(i.state);
1317 handleCancelShowConfirmTransferSite(i: CommentNode) {
1318 i.state.showConfirmTransferSite = false;
1319 i.setState(i.state);
1322 handleTransferSite(i: CommentNode) {
1323 let form: TransferSite = {
1324 person_id: i.props.node.comment_view.creator.id,
1327 WebSocketService.Instance.send(wsClient.transferSite(form));
1328 i.state.showConfirmTransferSite = false;
1329 i.setState(i.state);
1332 get isCommentNew(): boolean {
1333 let now = moment.utc().subtract(10, "minutes");
1334 let then = moment.utc(this.props.node.comment_view.comment.published);
1335 return now.isBefore(then);
1338 handleCommentCollapse(i: CommentNode) {
1339 i.state.collapsed = !i.state.collapsed;
1340 i.setState(i.state);
1344 handleViewSource(i: CommentNode) {
1345 i.state.viewSource = !i.state.viewSource;
1346 i.setState(i.state);
1349 handleShowAdvanced(i: CommentNode) {
1350 i.state.showAdvanced = !i.state.showAdvanced;
1351 i.setState(i.state);
1356 if (this.state.my_vote == 1) {
1358 } else if (this.state.my_vote == -1) {
1359 return "text-danger";
1361 return "text-muted";
1365 get pointsTippy(): string {
1366 let points = i18n.t("number_of_points", {
1367 count: this.state.score,
1368 formattedCount: this.state.score,
1371 let upvotes = i18n.t("number_of_upvotes", {
1372 count: this.state.upvotes,
1373 formattedCount: this.state.upvotes,
1376 let downvotes = i18n.t("number_of_downvotes", {
1377 count: this.state.downvotes,
1378 formattedCount: this.state.downvotes,
1381 return `${points} • ${upvotes} • ${downvotes}`;
1384 get expandText(): string {
1385 return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse");