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.bot_account && (
186 <div className="badge badge-light d-none d-sm-inline mr-2">
187 {i18n.t("bot_account").toLowerCase()}
190 {(cv.creator_banned_from_community || cv.creator.banned) && (
191 <div className="badge badge-danger mr-2">
195 {this.props.showCommunity && (
197 <span class="mx-1">{i18n.t("to")}</span>
198 <CommunityLink community={cv.community} />
199 <span class="mx-2">•</span>
200 <Link className="mr-2" to={`/post/${cv.post.id}`}>
206 class="btn btn-sm text-muted"
207 onClick={linkEvent(this, this.handleCommentCollapse)}
208 aria-label={this.expandText}
209 data-tippy-content={this.expandText}
211 {this.state.collapsed ? (
212 <Icon icon="plus-square" classes="icon-inline" />
214 <Icon icon="minus-square" classes="icon-inline" />
218 {/* This is an expanding spacer for mobile */}
219 <div className="mr-lg-5 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
223 className={`unselectable pointer ${this.scoreColor}`}
224 onClick={linkEvent(node, this.handleCommentUpvote)}
225 data-tippy-content={this.pointsTippy}
228 class="mr-1 font-weight-bold"
229 aria-label={i18n.t("number_of_points", {
230 count: this.state.score,
231 formattedCount: this.state.score,
234 {numToSI(this.state.score)}
237 <span className="mr-1">•</span>
241 <MomentTime data={cv.comment} />
244 {/* end of user row */}
245 {this.state.showEdit && (
249 onReplyCancel={this.handleReplyCancel}
250 disabled={this.props.locked}
254 {!this.state.showEdit && !this.state.collapsed && (
256 {this.state.viewSource ? (
257 <pre>{this.commentUnlessRemoved}</pre>
261 dangerouslySetInnerHTML={mdToHtml(
262 this.commentUnlessRemoved
266 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
267 {this.props.showContext && this.linkBtn()}
268 {this.props.markable && (
270 class="btn btn-link btn-animate text-muted"
271 onClick={linkEvent(this, this.handleMarkRead)}
273 this.commentOrMentionRead
274 ? i18n.t("mark_as_unread")
275 : i18n.t("mark_as_read")
278 this.commentOrMentionRead
279 ? i18n.t("mark_as_unread")
280 : i18n.t("mark_as_read")
283 {this.state.readLoading ? (
288 classes={`icon-inline ${
289 this.commentOrMentionRead && "text-success"
295 {UserService.Instance.myUserInfo && !this.props.viewOnly && (
298 className={`btn btn-link btn-animate ${
299 this.state.my_vote == 1 ? "text-info" : "text-muted"
301 onClick={linkEvent(node, this.handleCommentUpvote)}
302 data-tippy-content={i18n.t("upvote")}
303 aria-label={i18n.t("upvote")}
305 <Icon icon="arrow-up1" classes="icon-inline" />
307 this.state.upvotes !== this.state.score && (
309 {numToSI(this.state.upvotes)}
313 {this.props.enableDownvotes && (
315 className={`btn btn-link btn-animate ${
316 this.state.my_vote == -1
320 onClick={linkEvent(node, this.handleCommentDownvote)}
321 data-tippy-content={i18n.t("downvote")}
322 aria-label={i18n.t("downvote")}
324 <Icon icon="arrow-down1" classes="icon-inline" />
326 this.state.upvotes !== this.state.score && (
328 {numToSI(this.state.downvotes)}
334 class="btn btn-link btn-animate text-muted"
335 onClick={linkEvent(this, this.handleReplyClick)}
336 data-tippy-content={i18n.t("reply")}
337 aria-label={i18n.t("reply")}
339 <Icon icon="reply1" classes="icon-inline" />
341 {!this.state.showAdvanced ? (
343 className="btn btn-link btn-animate text-muted"
344 onClick={linkEvent(this, this.handleShowAdvanced)}
345 data-tippy-content={i18n.t("more")}
346 aria-label={i18n.t("more")}
348 <Icon icon="more-vertical" classes="icon-inline" />
352 {!this.myComment && (
354 <button class="btn btn-link btn-animate">
356 className="text-muted"
357 to={`/create_private_message/recipient/${cv.creator.id}`}
358 title={i18n.t("message").toLowerCase()}
364 class="btn btn-link btn-animate text-muted"
367 this.handleShowReportDialog
369 data-tippy-content={i18n.t(
372 aria-label={i18n.t("show_report_dialog")}
377 class="btn btn-link btn-animate text-muted"
380 this.handleBlockUserClick
382 data-tippy-content={i18n.t("block_user")}
383 aria-label={i18n.t("block_user")}
385 <Icon icon="slash" />
390 class="btn btn-link btn-animate text-muted"
393 this.handleSaveCommentClick
396 cv.saved ? i18n.t("unsave") : i18n.t("save")
399 cv.saved ? i18n.t("unsave") : i18n.t("save")
402 {this.state.saveLoading ? (
407 classes={`icon-inline ${
408 cv.saved && "text-warning"
414 className="btn btn-link btn-animate text-muted"
415 onClick={linkEvent(this, this.handleViewSource)}
416 data-tippy-content={i18n.t("view_source")}
417 aria-label={i18n.t("view_source")}
421 classes={`icon-inline ${
422 this.state.viewSource && "text-success"
429 class="btn btn-link btn-animate text-muted"
430 onClick={linkEvent(this, this.handleEditClick)}
431 data-tippy-content={i18n.t("edit")}
432 aria-label={i18n.t("edit")}
434 <Icon icon="edit" classes="icon-inline" />
437 class="btn btn-link btn-animate text-muted"
440 this.handleDeleteClick
455 classes={`icon-inline ${
456 cv.comment.deleted && "text-danger"
462 {/* Admins and mods can remove comments */}
463 {(this.canMod || this.canAdmin) && (
465 {!cv.comment.removed ? (
467 class="btn btn-link btn-animate text-muted"
470 this.handleModRemoveShow
472 aria-label={i18n.t("remove")}
478 class="btn btn-link btn-animate text-muted"
481 this.handleModRemoveSubmit
483 aria-label={i18n.t("restore")}
490 {/* Mods can ban from community, and appoint as mods to community */}
494 (!cv.creator_banned_from_community ? (
496 class="btn btn-link btn-animate text-muted"
499 this.handleModBanFromCommunityShow
501 aria-label={i18n.t("ban")}
507 class="btn btn-link btn-animate text-muted"
510 this.handleModBanFromCommunitySubmit
512 aria-label={i18n.t("unban")}
517 {!cv.creator_banned_from_community &&
518 (!this.state.showConfirmAppointAsMod ? (
520 class="btn btn-link btn-animate text-muted"
523 this.handleShowConfirmAppointAsMod
527 ? i18n.t("remove_as_mod")
528 : i18n.t("appoint_as_mod")
532 ? i18n.t("remove_as_mod")
533 : i18n.t("appoint_as_mod")}
538 class="btn btn-link btn-animate text-muted"
539 aria-label={i18n.t("are_you_sure")}
541 {i18n.t("are_you_sure")}
544 class="btn btn-link btn-animate text-muted"
547 this.handleAddModToCommunity
549 aria-label={i18n.t("yes")}
554 class="btn btn-link btn-animate text-muted"
557 this.handleCancelConfirmAppointAsMod
559 aria-label={i18n.t("no")}
567 {/* Community creators and admins can transfer community to another mod */}
568 {(this.amCommunityCreator || this.canAdmin) &&
571 (!this.state.showConfirmTransferCommunity ? (
573 class="btn btn-link btn-animate text-muted"
576 this.handleShowConfirmTransferCommunity
578 aria-label={i18n.t("transfer_community")}
580 {i18n.t("transfer_community")}
585 class="btn btn-link btn-animate text-muted"
586 aria-label={i18n.t("are_you_sure")}
588 {i18n.t("are_you_sure")}
591 class="btn btn-link btn-animate text-muted"
594 this.handleTransferCommunity
596 aria-label={i18n.t("yes")}
601 class="btn btn-link btn-animate text-muted"
605 .handleCancelShowConfirmTransferCommunity
607 aria-label={i18n.t("no")}
613 {/* Admins can ban from all, and appoint other admins */}
617 (!cv.creator.banned ? (
619 class="btn btn-link btn-animate text-muted"
622 this.handleModBanShow
624 aria-label={i18n.t("ban_from_site")}
626 {i18n.t("ban_from_site")}
630 class="btn btn-link btn-animate text-muted"
633 this.handleModBanSubmit
635 aria-label={i18n.t("unban_from_site")}
637 {i18n.t("unban_from_site")}
640 {!cv.creator.banned &&
642 (!this.state.showConfirmAppointAsAdmin ? (
644 class="btn btn-link btn-animate text-muted"
647 this.handleShowConfirmAppointAsAdmin
651 ? i18n.t("remove_as_admin")
652 : i18n.t("appoint_as_admin")
656 ? i18n.t("remove_as_admin")
657 : i18n.t("appoint_as_admin")}
661 <button class="btn btn-link btn-animate text-muted">
662 {i18n.t("are_you_sure")}
665 class="btn btn-link btn-animate text-muted"
670 aria-label={i18n.t("yes")}
675 class="btn btn-link btn-animate text-muted"
678 this.handleCancelConfirmAppointAsAdmin
680 aria-label={i18n.t("no")}
688 {/* Site Creator can transfer to another admin */}
689 {this.amSiteCreator &&
692 (!this.state.showConfirmTransferSite ? (
694 class="btn btn-link btn-animate text-muted"
697 this.handleShowConfirmTransferSite
699 aria-label={i18n.t("transfer_site")}
701 {i18n.t("transfer_site")}
706 class="btn btn-link btn-animate text-muted"
707 aria-label={i18n.t("are_you_sure")}
709 {i18n.t("are_you_sure")}
712 class="btn btn-link btn-animate text-muted"
715 this.handleTransferSite
717 aria-label={i18n.t("yes")}
722 class="btn btn-link btn-animate text-muted"
725 this.handleCancelShowConfirmTransferSite
727 aria-label={i18n.t("no")}
738 {/* end of button group */}
743 {/* end of details */}
744 {this.state.showRemoveDialog && (
747 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
751 htmlFor={`mod-remove-reason-${cv.comment.id}`}
757 id={`mod-remove-reason-${cv.comment.id}`}
758 class="form-control mr-2"
759 placeholder={i18n.t("reason")}
760 value={this.state.removeReason}
761 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
765 class="btn btn-secondary"
766 aria-label={i18n.t("remove_comment")}
768 {i18n.t("remove_comment")}
772 {this.state.showReportDialog && (
775 onSubmit={linkEvent(this, this.handleReportSubmit)}
777 <label class="sr-only" htmlFor={`report-reason-${cv.comment.id}`}>
783 id={`report-reason-${cv.comment.id}`}
784 class="form-control mr-2"
785 placeholder={i18n.t("reason")}
786 value={this.state.reportReason}
787 onInput={linkEvent(this, this.handleReportReasonChange)}
791 class="btn btn-secondary"
792 aria-label={i18n.t("create_report")}
794 {i18n.t("create_report")}
798 {this.state.showBanDialog && (
799 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
800 <div class="form-group row">
802 class="col-form-label"
803 htmlFor={`mod-ban-reason-${cv.comment.id}`}
809 id={`mod-ban-reason-${cv.comment.id}`}
810 class="form-control mr-2"
811 placeholder={i18n.t("reason")}
812 value={this.state.banReason}
813 onInput={linkEvent(this, this.handleModBanReasonChange)}
815 <div class="form-group">
816 <div class="form-check">
818 class="form-check-input"
819 id="mod-ban-remove-data"
821 checked={this.state.removeData}
822 onChange={linkEvent(this, this.handleModRemoveDataChange)}
825 class="form-check-label"
826 htmlFor="mod-ban-remove-data"
827 title={i18n.t("remove_content_more")}
829 {i18n.t("remove_content")}
834 {/* TODO hold off on expires until later */}
835 {/* <div class="form-group row"> */}
836 {/* <label class="col-form-label">Expires</label> */}
837 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
839 <div class="form-group row">
842 class="btn btn-secondary"
843 aria-label={i18n.t("ban")}
845 {i18n.t("ban")} {cv.creator.name}
850 {this.state.showReply && (
853 onReplyCancel={this.handleReplyCancel}
854 disabled={this.props.locked}
858 {node.children && !this.state.collapsed && (
860 nodes={node.children}
861 locked={this.props.locked}
862 moderators={this.props.moderators}
863 admins={this.props.admins}
864 postCreatorId={this.props.postCreatorId}
865 enableDownvotes={this.props.enableDownvotes}
868 {/* A collapsed clearfix */}
869 {this.state.collapsed && <div class="row col-12"></div>}
874 get commentOrMentionRead() {
875 let cv = this.props.node.comment_view;
876 return this.isPersonMentionType(cv)
877 ? cv.person_mention.read
881 linkBtn(small = false) {
882 let cv = this.props.node.comment_view;
885 className={`btn ${small && "btn-sm"} btn-link btn-animate text-muted`}
886 to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
887 title={this.props.showContext ? i18n.t("show_context") : i18n.t("link")}
889 <Icon icon="link" classes="icon-inline" />
898 get myComment(): boolean {
900 this.props.node.comment_view.creator.id ==
901 UserService.Instance.myUserInfo?.local_user_view.person.id
905 get isMod(): boolean {
907 this.props.moderators &&
909 this.props.moderators.map(m => m.moderator.id),
910 this.props.node.comment_view.creator.id
915 get isAdmin(): boolean {
919 this.props.admins.map(a => a.person.id),
920 this.props.node.comment_view.creator.id
925 get isPostCreator(): boolean {
926 return this.props.node.comment_view.creator.id == this.props.postCreatorId;
929 get canMod(): boolean {
930 if (this.props.admins && this.props.moderators) {
931 let adminsThenMods = this.props.admins
932 .map(a => a.person.id)
933 .concat(this.props.moderators.map(m => m.moderator.id));
936 UserService.Instance.myUserInfo,
938 this.props.node.comment_view.creator.id
945 get canAdmin(): boolean {
949 UserService.Instance.myUserInfo,
950 this.props.admins.map(a => a.person.id),
951 this.props.node.comment_view.creator.id
956 get amCommunityCreator(): boolean {
958 this.props.moderators &&
959 UserService.Instance.myUserInfo &&
960 this.props.node.comment_view.creator.id !=
961 UserService.Instance.myUserInfo.local_user_view.person.id &&
962 UserService.Instance.myUserInfo.local_user_view.person.id ==
963 this.props.moderators[0].moderator.id
967 get amSiteCreator(): boolean {
970 UserService.Instance.myUserInfo &&
971 this.props.node.comment_view.creator.id !=
972 UserService.Instance.myUserInfo.local_user_view.person.id &&
973 UserService.Instance.myUserInfo.local_user_view.person.id ==
974 this.props.admins[0].person.id
978 get commentUnlessRemoved(): string {
979 let comment = this.props.node.comment_view.comment;
980 return comment.removed
981 ? `*${i18n.t("removed")}*`
983 ? `*${i18n.t("deleted")}*`
987 handleReplyClick(i: CommentNode) {
988 i.state.showReply = true;
992 handleEditClick(i: CommentNode) {
993 i.state.showEdit = true;
997 handleBlockUserClick(i: CommentNode) {
998 let blockUserForm: BlockPerson = {
999 person_id: i.props.node.comment_view.creator.id,
1003 WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
1006 handleDeleteClick(i: CommentNode) {
1007 let comment = i.props.node.comment_view.comment;
1008 let deleteForm: DeleteComment = {
1009 comment_id: comment.id,
1010 deleted: !comment.deleted,
1013 WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
1016 handleSaveCommentClick(i: CommentNode) {
1017 let cv = i.props.node.comment_view;
1018 let save = cv.saved == undefined ? true : !cv.saved;
1019 let form: SaveComment = {
1020 comment_id: cv.comment.id,
1025 WebSocketService.Instance.send(wsClient.saveComment(form));
1027 i.state.saveLoading = true;
1028 i.setState(this.state);
1031 handleReplyCancel() {
1032 this.state.showReply = false;
1033 this.state.showEdit = false;
1034 this.setState(this.state);
1037 handleCommentUpvote(i: CommentNodeI, event: any) {
1038 event.preventDefault();
1039 let new_vote = this.state.my_vote == 1 ? 0 : 1;
1041 if (this.state.my_vote == 1) {
1043 this.state.upvotes--;
1044 } else if (this.state.my_vote == -1) {
1045 this.state.downvotes--;
1046 this.state.upvotes++;
1047 this.state.score += 2;
1049 this.state.upvotes++;
1053 this.state.my_vote = new_vote;
1055 let form: CreateCommentLike = {
1056 comment_id: i.comment_view.comment.id,
1057 score: this.state.my_vote,
1061 WebSocketService.Instance.send(wsClient.likeComment(form));
1062 this.setState(this.state);
1066 handleCommentDownvote(i: CommentNodeI, event: any) {
1067 event.preventDefault();
1068 let new_vote = this.state.my_vote == -1 ? 0 : -1;
1070 if (this.state.my_vote == 1) {
1071 this.state.score -= 2;
1072 this.state.upvotes--;
1073 this.state.downvotes++;
1074 } else if (this.state.my_vote == -1) {
1075 this.state.downvotes--;
1078 this.state.downvotes++;
1082 this.state.my_vote = new_vote;
1084 let form: CreateCommentLike = {
1085 comment_id: i.comment_view.comment.id,
1086 score: this.state.my_vote,
1090 WebSocketService.Instance.send(wsClient.likeComment(form));
1091 this.setState(this.state);
1095 handleShowReportDialog(i: CommentNode) {
1096 i.state.showReportDialog = !i.state.showReportDialog;
1097 i.setState(i.state);
1100 handleReportReasonChange(i: CommentNode, event: any) {
1101 i.state.reportReason = event.target.value;
1102 i.setState(i.state);
1105 handleReportSubmit(i: CommentNode) {
1106 let comment = i.props.node.comment_view.comment;
1107 let form: CreateCommentReport = {
1108 comment_id: comment.id,
1109 reason: i.state.reportReason,
1112 WebSocketService.Instance.send(wsClient.createCommentReport(form));
1114 i.state.showReportDialog = false;
1115 i.setState(i.state);
1118 handleModRemoveShow(i: CommentNode) {
1119 i.state.showRemoveDialog = !i.state.showRemoveDialog;
1120 i.state.showBanDialog = false;
1121 i.setState(i.state);
1124 handleModRemoveReasonChange(i: CommentNode, event: any) {
1125 i.state.removeReason = event.target.value;
1126 i.setState(i.state);
1129 handleModRemoveDataChange(i: CommentNode, event: any) {
1130 i.state.removeData = event.target.checked;
1131 i.setState(i.state);
1134 handleModRemoveSubmit(i: CommentNode) {
1135 let comment = i.props.node.comment_view.comment;
1136 let form: RemoveComment = {
1137 comment_id: comment.id,
1138 removed: !comment.removed,
1139 reason: i.state.removeReason,
1142 WebSocketService.Instance.send(wsClient.removeComment(form));
1144 i.state.showRemoveDialog = false;
1145 i.setState(i.state);
1148 isPersonMentionType(
1149 item: CommentView | PersonMentionView
1150 ): item is PersonMentionView {
1151 return (item as PersonMentionView).person_mention?.id !== undefined;
1154 handleMarkRead(i: CommentNode) {
1155 if (i.isPersonMentionType(i.props.node.comment_view)) {
1156 let form: MarkPersonMentionAsRead = {
1157 person_mention_id: i.props.node.comment_view.person_mention.id,
1158 read: !i.props.node.comment_view.person_mention.read,
1161 WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
1163 let form: MarkCommentAsRead = {
1164 comment_id: i.props.node.comment_view.comment.id,
1165 read: !i.props.node.comment_view.comment.read,
1168 WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
1171 i.state.readLoading = true;
1172 i.setState(this.state);
1175 handleModBanFromCommunityShow(i: CommentNode) {
1176 i.state.showBanDialog = true;
1177 i.state.banType = BanType.Community;
1178 i.state.showRemoveDialog = false;
1179 i.setState(i.state);
1182 handleModBanShow(i: CommentNode) {
1183 i.state.showBanDialog = true;
1184 i.state.banType = BanType.Site;
1185 i.state.showRemoveDialog = false;
1186 i.setState(i.state);
1189 handleModBanReasonChange(i: CommentNode, event: any) {
1190 i.state.banReason = event.target.value;
1191 i.setState(i.state);
1194 handleModBanExpiresChange(i: CommentNode, event: any) {
1195 i.state.banExpires = event.target.value;
1196 i.setState(i.state);
1199 handleModBanFromCommunitySubmit(i: CommentNode) {
1200 i.state.banType = BanType.Community;
1201 i.setState(i.state);
1202 i.handleModBanBothSubmit(i);
1205 handleModBanSubmit(i: CommentNode) {
1206 i.state.banType = BanType.Site;
1207 i.setState(i.state);
1208 i.handleModBanBothSubmit(i);
1211 handleModBanBothSubmit(i: CommentNode) {
1212 let cv = i.props.node.comment_view;
1214 if (i.state.banType == BanType.Community) {
1215 // If its an unban, restore all their data
1216 let ban = !cv.creator_banned_from_community;
1218 i.state.removeData = false;
1220 let form: BanFromCommunity = {
1221 person_id: cv.creator.id,
1222 community_id: cv.community.id,
1224 remove_data: i.state.removeData,
1225 reason: i.state.banReason,
1226 expires: getUnixTime(i.state.banExpires),
1229 WebSocketService.Instance.send(wsClient.banFromCommunity(form));
1231 // If its an unban, restore all their data
1232 let ban = !cv.creator.banned;
1234 i.state.removeData = false;
1236 let form: BanPerson = {
1237 person_id: cv.creator.id,
1239 remove_data: i.state.removeData,
1240 reason: i.state.banReason,
1241 expires: getUnixTime(i.state.banExpires),
1244 WebSocketService.Instance.send(wsClient.banPerson(form));
1247 i.state.showBanDialog = false;
1248 i.setState(i.state);
1251 handleShowConfirmAppointAsMod(i: CommentNode) {
1252 i.state.showConfirmAppointAsMod = true;
1253 i.setState(i.state);
1256 handleCancelConfirmAppointAsMod(i: CommentNode) {
1257 i.state.showConfirmAppointAsMod = false;
1258 i.setState(i.state);
1261 handleAddModToCommunity(i: CommentNode) {
1262 let cv = i.props.node.comment_view;
1263 let form: AddModToCommunity = {
1264 person_id: cv.creator.id,
1265 community_id: cv.community.id,
1269 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
1270 i.state.showConfirmAppointAsMod = false;
1271 i.setState(i.state);
1274 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1275 i.state.showConfirmAppointAsAdmin = true;
1276 i.setState(i.state);
1279 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1280 i.state.showConfirmAppointAsAdmin = false;
1281 i.setState(i.state);
1284 handleAddAdmin(i: CommentNode) {
1285 let form: AddAdmin = {
1286 person_id: i.props.node.comment_view.creator.id,
1290 WebSocketService.Instance.send(wsClient.addAdmin(form));
1291 i.state.showConfirmAppointAsAdmin = false;
1292 i.setState(i.state);
1295 handleShowConfirmTransferCommunity(i: CommentNode) {
1296 i.state.showConfirmTransferCommunity = true;
1297 i.setState(i.state);
1300 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1301 i.state.showConfirmTransferCommunity = false;
1302 i.setState(i.state);
1305 handleTransferCommunity(i: CommentNode) {
1306 let cv = i.props.node.comment_view;
1307 let form: TransferCommunity = {
1308 community_id: cv.community.id,
1309 person_id: cv.creator.id,
1312 WebSocketService.Instance.send(wsClient.transferCommunity(form));
1313 i.state.showConfirmTransferCommunity = false;
1314 i.setState(i.state);
1317 handleShowConfirmTransferSite(i: CommentNode) {
1318 i.state.showConfirmTransferSite = true;
1319 i.setState(i.state);
1322 handleCancelShowConfirmTransferSite(i: CommentNode) {
1323 i.state.showConfirmTransferSite = false;
1324 i.setState(i.state);
1327 handleTransferSite(i: CommentNode) {
1328 let form: TransferSite = {
1329 person_id: i.props.node.comment_view.creator.id,
1332 WebSocketService.Instance.send(wsClient.transferSite(form));
1333 i.state.showConfirmTransferSite = false;
1334 i.setState(i.state);
1337 get isCommentNew(): boolean {
1338 let now = moment.utc().subtract(10, "minutes");
1339 let then = moment.utc(this.props.node.comment_view.comment.published);
1340 return now.isBefore(then);
1343 handleCommentCollapse(i: CommentNode) {
1344 i.state.collapsed = !i.state.collapsed;
1345 i.setState(i.state);
1349 handleViewSource(i: CommentNode) {
1350 i.state.viewSource = !i.state.viewSource;
1351 i.setState(i.state);
1354 handleShowAdvanced(i: CommentNode) {
1355 i.state.showAdvanced = !i.state.showAdvanced;
1356 i.setState(i.state);
1361 if (this.state.my_vote == 1) {
1363 } else if (this.state.my_vote == -1) {
1364 return "text-danger";
1366 return "text-muted";
1370 get pointsTippy(): string {
1371 let points = i18n.t("number_of_points", {
1372 count: this.state.score,
1373 formattedCount: this.state.score,
1376 let upvotes = i18n.t("number_of_upvotes", {
1377 count: this.state.upvotes,
1378 formattedCount: this.state.upvotes,
1381 let downvotes = i18n.t("number_of_downvotes", {
1382 count: this.state.downvotes,
1383 formattedCount: this.state.downvotes,
1386 return `${points} • ${upvotes} • ${downvotes}`;
1389 get expandText(): string {
1390 return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse");