1 import { Left, None, Option, Some } from "@sniptt/monads";
2 import classNames from "classnames";
3 import { Component, linkEvent } from "inferno";
4 import { Link } from "inferno-router";
12 CommunityModeratorView,
17 MarkPersonMentionAsRead,
24 } from "lemmy-js-client";
25 import moment from "moment";
26 import { i18n } from "../../i18next";
27 import { BanType, CommentNode as CommentNodeI } from "../../interfaces";
28 import { UserService, WebSocketService } from "../../services";
45 import { Icon, Spinner } from "../common/icon";
46 import { MomentTime } from "../common/moment-time";
47 import { CommunityLink } from "../community/community-link";
48 import { PersonListing } from "../person/person-listing";
49 import { CommentForm } from "./comment-form";
50 import { CommentNodes } from "./comment-nodes";
52 interface CommentNodeState {
55 showRemoveDialog: boolean;
56 removeReason: Option<string>;
57 showBanDialog: boolean;
59 banReason: Option<string>;
60 banExpireDays: Option<number>;
62 showConfirmTransferSite: boolean;
63 showConfirmTransferCommunity: boolean;
64 showConfirmAppointAsMod: boolean;
65 showConfirmAppointAsAdmin: boolean;
68 showAdvanced: boolean;
69 showReportDialog: boolean;
71 my_vote: Option<number>;
80 interface CommentNodeProps {
82 moderators: Option<CommunityModeratorView[]>;
83 admins: Option<PersonViewSafe[]>;
89 showContext?: boolean;
90 showCommunity?: boolean;
91 enableDownvotes: boolean;
94 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
95 private emptyState: CommentNodeState = {
98 showRemoveDialog: false,
100 showBanDialog: false,
104 banType: BanType.Community,
108 showConfirmTransferSite: false,
109 showConfirmTransferCommunity: false,
110 showConfirmAppointAsMod: false,
111 showConfirmAppointAsAdmin: false,
112 showReportDialog: false,
114 my_vote: this.props.node.comment_view.my_vote,
115 score: this.props.node.comment_view.counts.score,
116 upvotes: this.props.node.comment_view.counts.upvotes,
117 downvotes: this.props.node.comment_view.counts.downvotes,
118 borderColor: this.props.node.depth
119 ? colorList[this.props.node.depth % colorList.length]
125 constructor(props: any, context: any) {
126 super(props, context);
128 this.state = this.emptyState;
129 this.handleReplyCancel = this.handleReplyCancel.bind(this);
130 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
131 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
134 // TODO see if there's a better way to do this, and all willReceiveProps
135 componentWillReceiveProps(nextProps: CommentNodeProps) {
136 let cv = nextProps.node.comment_view;
137 this.state.my_vote = cv.my_vote;
138 this.state.upvotes = cv.counts.upvotes;
139 this.state.downvotes = cv.counts.downvotes;
140 this.state.score = cv.counts.score;
141 this.state.readLoading = false;
142 this.state.saveLoading = false;
143 this.setState(this.state);
147 let node = this.props.node;
148 let cv = this.props.node.comment_view;
150 let canMod_ = canMod(
151 this.props.moderators,
155 let canAdmin_ = canAdmin(this.props.admins, cv.creator.id);
156 let isMod_ = isMod(this.props.moderators, cv.creator.id);
157 let isAdmin_ = isAdmin(this.props.admins, cv.creator.id);
158 let amCommunityCreator_ = amCommunityCreator(
159 this.props.moderators,
165 className={`comment ${
166 cv.comment.parent_id.isSome() && !this.props.noIndent ? "ml-1" : ""
170 id={`comment-${cv.comment.id}`}
171 className={`details comment-node py-2 ${
172 !this.props.noBorder ? "border-top border-light" : ""
173 } ${this.isCommentNew ? "mark" : ""}`}
175 !this.props.noIndent &&
176 cv.comment.parent_id.isSome() &&
177 `border-left: 2px ${this.state.borderColor} solid !important`
182 !this.props.noIndent && cv.comment.parent_id.isSome() && "ml-2"
185 <div class="d-flex flex-wrap align-items-center text-muted small">
187 <PersonListing person={cv.creator} />
191 <div className="badge badge-light d-none d-sm-inline mr-2">
196 <div className="badge badge-light d-none d-sm-inline mr-2">
200 {this.isPostCreator && (
201 <div className="badge badge-light d-none d-sm-inline mr-2">
205 {cv.creator.bot_account && (
206 <div className="badge badge-light d-none d-sm-inline mr-2">
207 {i18n.t("bot_account").toLowerCase()}
210 {(cv.creator_banned_from_community || isBanned(cv.creator)) && (
211 <div className="badge badge-danger mr-2">
215 {this.props.showCommunity && (
217 <span class="mx-1">{i18n.t("to")}</span>
218 <CommunityLink community={cv.community} />
219 <span class="mx-2">•</span>
220 <Link className="mr-2" to={`/post/${cv.post.id}`}>
226 class="btn btn-sm text-muted"
227 onClick={linkEvent(this, this.handleCommentCollapse)}
228 aria-label={this.expandText}
229 data-tippy-content={this.expandText}
231 {this.state.collapsed ? (
232 <Icon icon="plus-square" classes="icon-inline" />
234 <Icon icon="minus-square" classes="icon-inline" />
238 {/* This is an expanding spacer for mobile */}
239 <div className="mr-lg-5 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
243 className={`unselectable pointer ${this.scoreColor}`}
244 onClick={linkEvent(node, this.handleCommentUpvote)}
245 data-tippy-content={this.pointsTippy}
248 class="mr-1 font-weight-bold"
249 aria-label={i18n.t("number_of_points", {
250 count: this.state.score,
251 formattedCount: this.state.score,
254 {numToSI(this.state.score)}
257 <span className="mr-1">•</span>
262 published={cv.comment.published}
263 updated={cv.comment.updated}
267 {/* end of user row */}
268 {this.state.showEdit && (
272 onReplyCancel={this.handleReplyCancel}
273 disabled={this.props.locked}
277 {!this.state.showEdit && !this.state.collapsed && (
279 {this.state.viewSource ? (
280 <pre>{this.commentUnlessRemoved}</pre>
284 dangerouslySetInnerHTML={mdToHtml(
285 this.commentUnlessRemoved
289 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
290 {this.props.showContext && this.linkBtn()}
291 {this.props.markable && (
293 class="btn btn-link btn-animate text-muted"
294 onClick={linkEvent(this, this.handleMarkRead)}
296 this.commentOrMentionRead
297 ? i18n.t("mark_as_unread")
298 : i18n.t("mark_as_read")
301 this.commentOrMentionRead
302 ? i18n.t("mark_as_unread")
303 : i18n.t("mark_as_read")
306 {this.state.readLoading ? (
311 classes={`icon-inline ${
312 this.commentOrMentionRead && "text-success"
318 {UserService.Instance.myUserInfo.isSome() &&
319 !this.props.viewOnly && (
322 className={`btn btn-link btn-animate ${
323 this.state.my_vote.unwrapOr(0) == 1
327 onClick={linkEvent(node, this.handleCommentUpvote)}
328 data-tippy-content={i18n.t("upvote")}
329 aria-label={i18n.t("upvote")}
331 <Icon icon="arrow-up1" classes="icon-inline" />
333 this.state.upvotes !== this.state.score && (
335 {numToSI(this.state.upvotes)}
339 {this.props.enableDownvotes && (
341 className={`btn btn-link btn-animate ${
342 this.state.my_vote.unwrapOr(0) == -1
348 this.handleCommentDownvote
350 data-tippy-content={i18n.t("downvote")}
351 aria-label={i18n.t("downvote")}
353 <Icon icon="arrow-down1" classes="icon-inline" />
355 this.state.upvotes !== this.state.score && (
357 {numToSI(this.state.downvotes)}
363 class="btn btn-link btn-animate text-muted"
364 onClick={linkEvent(this, this.handleReplyClick)}
365 data-tippy-content={i18n.t("reply")}
366 aria-label={i18n.t("reply")}
368 <Icon icon="reply1" classes="icon-inline" />
370 {!this.state.showAdvanced ? (
372 className="btn btn-link btn-animate text-muted"
373 onClick={linkEvent(this, this.handleShowAdvanced)}
374 data-tippy-content={i18n.t("more")}
375 aria-label={i18n.t("more")}
377 <Icon icon="more-vertical" classes="icon-inline" />
381 {!this.myComment && (
383 <button class="btn btn-link btn-animate">
385 className="text-muted"
386 to={`/create_private_message/recipient/${cv.creator.id}`}
387 title={i18n.t("message").toLowerCase()}
393 class="btn btn-link btn-animate text-muted"
396 this.handleShowReportDialog
398 data-tippy-content={i18n.t(
401 aria-label={i18n.t("show_report_dialog")}
406 class="btn btn-link btn-animate text-muted"
409 this.handleBlockUserClick
411 data-tippy-content={i18n.t("block_user")}
412 aria-label={i18n.t("block_user")}
414 <Icon icon="slash" />
419 class="btn btn-link btn-animate text-muted"
422 this.handleSaveCommentClick
425 cv.saved ? i18n.t("unsave") : i18n.t("save")
428 cv.saved ? i18n.t("unsave") : i18n.t("save")
431 {this.state.saveLoading ? (
436 classes={`icon-inline ${
437 cv.saved && "text-warning"
443 className="btn btn-link btn-animate text-muted"
444 onClick={linkEvent(this, this.handleViewSource)}
445 data-tippy-content={i18n.t("view_source")}
446 aria-label={i18n.t("view_source")}
450 classes={`icon-inline ${
451 this.state.viewSource && "text-success"
458 class="btn btn-link btn-animate text-muted"
463 data-tippy-content={i18n.t("edit")}
464 aria-label={i18n.t("edit")}
466 <Icon icon="edit" classes="icon-inline" />
469 class="btn btn-link btn-animate text-muted"
472 this.handleDeleteClick
487 classes={`icon-inline ${
488 cv.comment.deleted && "text-danger"
494 {/* Admins and mods can remove comments */}
495 {(canMod_ || canAdmin_) && (
497 {!cv.comment.removed ? (
499 class="btn btn-link btn-animate text-muted"
502 this.handleModRemoveShow
504 aria-label={i18n.t("remove")}
510 class="btn btn-link btn-animate text-muted"
513 this.handleModRemoveSubmit
515 aria-label={i18n.t("restore")}
522 {/* Mods can ban from community, and appoint as mods to community */}
526 (!cv.creator_banned_from_community ? (
528 class="btn btn-link btn-animate text-muted"
531 this.handleModBanFromCommunityShow
533 aria-label={i18n.t("ban")}
539 class="btn btn-link btn-animate text-muted"
542 this.handleModBanFromCommunitySubmit
544 aria-label={i18n.t("unban")}
549 {!cv.creator_banned_from_community &&
550 (!this.state.showConfirmAppointAsMod ? (
552 class="btn btn-link btn-animate text-muted"
555 this.handleShowConfirmAppointAsMod
559 ? i18n.t("remove_as_mod")
560 : i18n.t("appoint_as_mod")
564 ? i18n.t("remove_as_mod")
565 : i18n.t("appoint_as_mod")}
570 class="btn btn-link btn-animate text-muted"
571 aria-label={i18n.t("are_you_sure")}
573 {i18n.t("are_you_sure")}
576 class="btn btn-link btn-animate text-muted"
579 this.handleAddModToCommunity
581 aria-label={i18n.t("yes")}
586 class="btn btn-link btn-animate text-muted"
589 this.handleCancelConfirmAppointAsMod
591 aria-label={i18n.t("no")}
599 {/* Community creators and admins can transfer community to another mod */}
600 {(amCommunityCreator_ || canAdmin_) &&
603 (!this.state.showConfirmTransferCommunity ? (
605 class="btn btn-link btn-animate text-muted"
608 this.handleShowConfirmTransferCommunity
610 aria-label={i18n.t("transfer_community")}
612 {i18n.t("transfer_community")}
617 class="btn btn-link btn-animate text-muted"
618 aria-label={i18n.t("are_you_sure")}
620 {i18n.t("are_you_sure")}
623 class="btn btn-link btn-animate text-muted"
626 this.handleTransferCommunity
628 aria-label={i18n.t("yes")}
633 class="btn btn-link btn-animate text-muted"
637 .handleCancelShowConfirmTransferCommunity
639 aria-label={i18n.t("no")}
645 {/* Admins can ban from all, and appoint other admins */}
649 (!isBanned(cv.creator) ? (
651 class="btn btn-link btn-animate text-muted"
654 this.handleModBanShow
656 aria-label={i18n.t("ban_from_site")}
658 {i18n.t("ban_from_site")}
662 class="btn btn-link btn-animate text-muted"
665 this.handleModBanSubmit
667 aria-label={i18n.t("unban_from_site")}
669 {i18n.t("unban_from_site")}
672 {!isBanned(cv.creator) &&
674 (!this.state.showConfirmAppointAsAdmin ? (
676 class="btn btn-link btn-animate text-muted"
679 this.handleShowConfirmAppointAsAdmin
683 ? i18n.t("remove_as_admin")
684 : i18n.t("appoint_as_admin")
688 ? i18n.t("remove_as_admin")
689 : i18n.t("appoint_as_admin")}
693 <button class="btn btn-link btn-animate text-muted">
694 {i18n.t("are_you_sure")}
697 class="btn btn-link btn-animate text-muted"
702 aria-label={i18n.t("yes")}
707 class="btn btn-link btn-animate text-muted"
710 this.handleCancelConfirmAppointAsAdmin
712 aria-label={i18n.t("no")}
725 {/* end of button group */}
730 {/* end of details */}
731 {this.state.showRemoveDialog && (
734 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
738 htmlFor={`mod-remove-reason-${cv.comment.id}`}
744 id={`mod-remove-reason-${cv.comment.id}`}
745 class="form-control mr-2"
746 placeholder={i18n.t("reason")}
747 value={toUndefined(this.state.removeReason)}
748 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
752 class="btn btn-secondary"
753 aria-label={i18n.t("remove_comment")}
755 {i18n.t("remove_comment")}
759 {this.state.showReportDialog && (
762 onSubmit={linkEvent(this, this.handleReportSubmit)}
764 <label class="sr-only" htmlFor={`report-reason-${cv.comment.id}`}>
770 id={`report-reason-${cv.comment.id}`}
771 class="form-control mr-2"
772 placeholder={i18n.t("reason")}
773 value={this.state.reportReason}
774 onInput={linkEvent(this, this.handleReportReasonChange)}
778 class="btn btn-secondary"
779 aria-label={i18n.t("create_report")}
781 {i18n.t("create_report")}
785 {this.state.showBanDialog && (
786 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
787 <div class="form-group row col-12">
789 class="col-form-label"
790 htmlFor={`mod-ban-reason-${cv.comment.id}`}
796 id={`mod-ban-reason-${cv.comment.id}`}
797 class="form-control mr-2"
798 placeholder={i18n.t("reason")}
799 value={toUndefined(this.state.banReason)}
800 onInput={linkEvent(this, this.handleModBanReasonChange)}
803 class="col-form-label"
804 htmlFor={`mod-ban-expires-${cv.comment.id}`}
810 id={`mod-ban-expires-${cv.comment.id}`}
811 class="form-control mr-2"
812 placeholder={i18n.t("number_of_days")}
813 value={toUndefined(this.state.banExpireDays)}
814 onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
816 <div class="form-group">
817 <div class="form-check">
819 class="form-check-input"
820 id="mod-ban-remove-data"
822 checked={this.state.removeData}
823 onChange={linkEvent(this, this.handleModRemoveDataChange)}
826 class="form-check-label"
827 htmlFor="mod-ban-remove-data"
828 title={i18n.t("remove_content_more")}
830 {i18n.t("remove_content")}
835 {/* TODO hold off on expires until later */}
836 {/* <div class="form-group row"> */}
837 {/* <label class="col-form-label">Expires</label> */}
838 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
840 <div class="form-group row">
843 class="btn btn-secondary"
844 aria-label={i18n.t("ban")}
846 {i18n.t("ban")} {cv.creator.name}
851 {this.state.showReply && (
854 onReplyCancel={this.handleReplyCancel}
855 disabled={this.props.locked}
859 {!this.state.collapsed && node.children && (
861 nodes={node.children}
862 locked={this.props.locked}
863 moderators={this.props.moderators}
864 admins={this.props.admins}
865 maxCommentsShown={None}
866 enableDownvotes={this.props.enableDownvotes}
869 {/* A collapsed clearfix */}
870 {this.state.collapsed && <div class="row col-12"></div>}
875 get commentOrMentionRead() {
876 let cv = this.props.node.comment_view;
877 return this.isPersonMentionType(cv)
878 ? cv.person_mention.read
882 linkBtn(small = false) {
883 let cv = this.props.node.comment_view;
884 let classnames = classNames("btn btn-link btn-animate text-muted", {
888 let title = this.props.showContext
889 ? i18n.t("show_context")
895 className={classnames}
896 to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
899 <Icon icon="link" classes="icon-inline" />
902 <a className={classnames} title={title} href={cv.comment.ap_id}>
903 <Icon icon="fedilink" classes="icon-inline" />
914 get myComment(): boolean {
915 return UserService.Instance.myUserInfo
918 m.local_user_view.person.id == this.props.node.comment_view.creator.id
923 get isPostCreator(): boolean {
925 this.props.node.comment_view.creator.id ==
926 this.props.node.comment_view.post.creator_id
930 get commentUnlessRemoved(): string {
931 let comment = this.props.node.comment_view.comment;
932 return comment.removed
933 ? `*${i18n.t("removed")}*`
935 ? `*${i18n.t("deleted")}*`
939 handleReplyClick(i: CommentNode) {
940 i.state.showReply = true;
944 handleEditClick(i: CommentNode) {
945 i.state.showEdit = true;
949 handleBlockUserClick(i: CommentNode) {
950 let blockUserForm = new BlockPerson({
951 person_id: i.props.node.comment_view.creator.id,
953 auth: auth().unwrap(),
955 WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
958 handleDeleteClick(i: CommentNode) {
959 let comment = i.props.node.comment_view.comment;
960 let deleteForm = new DeleteComment({
961 comment_id: comment.id,
962 deleted: !comment.deleted,
963 auth: auth().unwrap(),
965 WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
968 handleSaveCommentClick(i: CommentNode) {
969 let cv = i.props.node.comment_view;
970 let save = cv.saved == undefined ? true : !cv.saved;
971 let form = new SaveComment({
972 comment_id: cv.comment.id,
974 auth: auth().unwrap(),
977 WebSocketService.Instance.send(wsClient.saveComment(form));
979 i.state.saveLoading = true;
980 i.setState(this.state);
983 handleReplyCancel() {
984 this.state.showReply = false;
985 this.state.showEdit = false;
986 this.setState(this.state);
989 handleCommentUpvote(i: CommentNodeI, event: any) {
990 event.preventDefault();
991 let myVote = this.state.my_vote.unwrapOr(0);
992 let newVote = myVote == 1 ? 0 : 1;
996 this.state.upvotes--;
997 } else if (myVote == -1) {
998 this.state.downvotes--;
999 this.state.upvotes++;
1000 this.state.score += 2;
1002 this.state.upvotes++;
1006 this.state.my_vote = Some(newVote);
1008 let form = new CreateCommentLike({
1009 comment_id: i.comment_view.comment.id,
1011 auth: auth().unwrap(),
1014 WebSocketService.Instance.send(wsClient.likeComment(form));
1015 this.setState(this.state);
1019 handleCommentDownvote(i: CommentNodeI, event: any) {
1020 event.preventDefault();
1021 let myVote = this.state.my_vote.unwrapOr(0);
1022 let newVote = myVote == -1 ? 0 : -1;
1025 this.state.score -= 2;
1026 this.state.upvotes--;
1027 this.state.downvotes++;
1028 } else if (myVote == -1) {
1029 this.state.downvotes--;
1032 this.state.downvotes++;
1036 this.state.my_vote = Some(newVote);
1038 let form = new CreateCommentLike({
1039 comment_id: i.comment_view.comment.id,
1041 auth: auth().unwrap(),
1044 WebSocketService.Instance.send(wsClient.likeComment(form));
1045 this.setState(this.state);
1049 handleShowReportDialog(i: CommentNode) {
1050 i.state.showReportDialog = !i.state.showReportDialog;
1051 i.setState(i.state);
1054 handleReportReasonChange(i: CommentNode, event: any) {
1055 i.state.reportReason = event.target.value;
1056 i.setState(i.state);
1059 handleReportSubmit(i: CommentNode) {
1060 let comment = i.props.node.comment_view.comment;
1061 let form = new CreateCommentReport({
1062 comment_id: comment.id,
1063 reason: i.state.reportReason,
1064 auth: auth().unwrap(),
1066 WebSocketService.Instance.send(wsClient.createCommentReport(form));
1068 i.state.showReportDialog = false;
1069 i.setState(i.state);
1072 handleModRemoveShow(i: CommentNode) {
1073 i.state.showRemoveDialog = !i.state.showRemoveDialog;
1074 i.state.showBanDialog = false;
1075 i.setState(i.state);
1078 handleModRemoveReasonChange(i: CommentNode, event: any) {
1079 i.state.removeReason = Some(event.target.value);
1080 i.setState(i.state);
1083 handleModRemoveDataChange(i: CommentNode, event: any) {
1084 i.state.removeData = event.target.checked;
1085 i.setState(i.state);
1088 handleModRemoveSubmit(i: CommentNode) {
1089 let comment = i.props.node.comment_view.comment;
1090 let form = new RemoveComment({
1091 comment_id: comment.id,
1092 removed: !comment.removed,
1093 reason: i.state.removeReason,
1094 auth: auth().unwrap(),
1096 WebSocketService.Instance.send(wsClient.removeComment(form));
1098 i.state.showRemoveDialog = false;
1099 i.setState(i.state);
1102 isPersonMentionType(
1103 item: CommentView | PersonMentionView
1104 ): item is PersonMentionView {
1105 return (item as PersonMentionView).person_mention?.id !== undefined;
1108 handleMarkRead(i: CommentNode) {
1109 if (i.isPersonMentionType(i.props.node.comment_view)) {
1110 let form = new MarkPersonMentionAsRead({
1111 person_mention_id: i.props.node.comment_view.person_mention.id,
1112 read: !i.props.node.comment_view.person_mention.read,
1113 auth: auth().unwrap(),
1115 WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
1117 let form = new MarkCommentAsRead({
1118 comment_id: i.props.node.comment_view.comment.id,
1119 read: !i.props.node.comment_view.comment.read,
1120 auth: auth().unwrap(),
1122 WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
1125 i.state.readLoading = true;
1126 i.setState(this.state);
1129 handleModBanFromCommunityShow(i: CommentNode) {
1130 i.state.showBanDialog = true;
1131 i.state.banType = BanType.Community;
1132 i.state.showRemoveDialog = false;
1133 i.setState(i.state);
1136 handleModBanShow(i: CommentNode) {
1137 i.state.showBanDialog = true;
1138 i.state.banType = BanType.Site;
1139 i.state.showRemoveDialog = false;
1140 i.setState(i.state);
1143 handleModBanReasonChange(i: CommentNode, event: any) {
1144 i.state.banReason = Some(event.target.value);
1145 i.setState(i.state);
1148 handleModBanExpireDaysChange(i: CommentNode, event: any) {
1149 i.state.banExpireDays = Some(event.target.value);
1150 i.setState(i.state);
1153 handleModBanFromCommunitySubmit(i: CommentNode) {
1154 i.state.banType = BanType.Community;
1155 i.setState(i.state);
1156 i.handleModBanBothSubmit(i);
1159 handleModBanSubmit(i: CommentNode) {
1160 i.state.banType = BanType.Site;
1161 i.setState(i.state);
1162 i.handleModBanBothSubmit(i);
1165 handleModBanBothSubmit(i: CommentNode) {
1166 let cv = i.props.node.comment_view;
1168 if (i.state.banType == BanType.Community) {
1169 // If its an unban, restore all their data
1170 let ban = !cv.creator_banned_from_community;
1172 i.state.removeData = false;
1174 let form = new BanFromCommunity({
1175 person_id: cv.creator.id,
1176 community_id: cv.community.id,
1178 remove_data: Some(i.state.removeData),
1179 reason: i.state.banReason,
1180 expires: i.state.banExpireDays.map(futureDaysToUnixTime),
1181 auth: auth().unwrap(),
1183 WebSocketService.Instance.send(wsClient.banFromCommunity(form));
1185 // If its an unban, restore all their data
1186 let ban = !cv.creator.banned;
1188 i.state.removeData = false;
1190 let form = new BanPerson({
1191 person_id: cv.creator.id,
1193 remove_data: Some(i.state.removeData),
1194 reason: i.state.banReason,
1195 expires: i.state.banExpireDays.map(futureDaysToUnixTime),
1196 auth: auth().unwrap(),
1198 WebSocketService.Instance.send(wsClient.banPerson(form));
1201 i.state.showBanDialog = false;
1202 i.setState(i.state);
1205 handleShowConfirmAppointAsMod(i: CommentNode) {
1206 i.state.showConfirmAppointAsMod = true;
1207 i.setState(i.state);
1210 handleCancelConfirmAppointAsMod(i: CommentNode) {
1211 i.state.showConfirmAppointAsMod = false;
1212 i.setState(i.state);
1215 handleAddModToCommunity(i: CommentNode) {
1216 let cv = i.props.node.comment_view;
1217 let form = new AddModToCommunity({
1218 person_id: cv.creator.id,
1219 community_id: cv.community.id,
1220 added: !isMod(i.props.moderators, cv.creator.id),
1221 auth: auth().unwrap(),
1223 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
1224 i.state.showConfirmAppointAsMod = false;
1225 i.setState(i.state);
1228 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1229 i.state.showConfirmAppointAsAdmin = true;
1230 i.setState(i.state);
1233 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1234 i.state.showConfirmAppointAsAdmin = false;
1235 i.setState(i.state);
1238 handleAddAdmin(i: CommentNode) {
1239 let creatorId = i.props.node.comment_view.creator.id;
1240 let form = new AddAdmin({
1241 person_id: creatorId,
1242 added: !isAdmin(i.props.admins, creatorId),
1243 auth: auth().unwrap(),
1245 WebSocketService.Instance.send(wsClient.addAdmin(form));
1246 i.state.showConfirmAppointAsAdmin = false;
1247 i.setState(i.state);
1250 handleShowConfirmTransferCommunity(i: CommentNode) {
1251 i.state.showConfirmTransferCommunity = true;
1252 i.setState(i.state);
1255 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1256 i.state.showConfirmTransferCommunity = false;
1257 i.setState(i.state);
1260 handleTransferCommunity(i: CommentNode) {
1261 let cv = i.props.node.comment_view;
1262 let form = new TransferCommunity({
1263 community_id: cv.community.id,
1264 person_id: cv.creator.id,
1265 auth: auth().unwrap(),
1267 WebSocketService.Instance.send(wsClient.transferCommunity(form));
1268 i.state.showConfirmTransferCommunity = false;
1269 i.setState(i.state);
1272 handleShowConfirmTransferSite(i: CommentNode) {
1273 i.state.showConfirmTransferSite = true;
1274 i.setState(i.state);
1277 handleCancelShowConfirmTransferSite(i: CommentNode) {
1278 i.state.showConfirmTransferSite = false;
1279 i.setState(i.state);
1282 get isCommentNew(): boolean {
1283 let now = moment.utc().subtract(10, "minutes");
1284 let then = moment.utc(this.props.node.comment_view.comment.published);
1285 return now.isBefore(then);
1288 handleCommentCollapse(i: CommentNode) {
1289 i.state.collapsed = !i.state.collapsed;
1290 i.setState(i.state);
1294 handleViewSource(i: CommentNode) {
1295 i.state.viewSource = !i.state.viewSource;
1296 i.setState(i.state);
1299 handleShowAdvanced(i: CommentNode) {
1300 i.state.showAdvanced = !i.state.showAdvanced;
1301 i.setState(i.state);
1306 if (this.state.my_vote.unwrapOr(0) == 1) {
1308 } else if (this.state.my_vote.unwrapOr(0) == -1) {
1309 return "text-danger";
1311 return "text-muted";
1315 get pointsTippy(): string {
1316 let points = i18n.t("number_of_points", {
1317 count: this.state.score,
1318 formattedCount: this.state.score,
1321 let upvotes = i18n.t("number_of_upvotes", {
1322 count: this.state.upvotes,
1323 formattedCount: this.state.upvotes,
1326 let downvotes = i18n.t("number_of_downvotes", {
1327 count: this.state.downvotes,
1328 formattedCount: this.state.downvotes,
1331 return `${points} • ${upvotes} • ${downvotes}`;
1334 get expandText(): string {
1335 return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse");