1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
12 CommunityModeratorView,
20 } from "lemmy-js-client";
21 import { CommentNode as CommentNodeI, BanType } from "../interfaces";
22 import { WebSocketService, UserService } from "../services";
33 import moment from "moment";
34 import { MomentTime } from "./moment-time";
35 import { CommentForm } from "./comment-form";
36 import { CommentNodes } from "./comment-nodes";
37 import { UserListing } from "./user-listing";
38 import { CommunityLink } from "./community-link";
39 import { Icon, Spinner } from "./icon";
40 import { i18n } from "../i18next";
42 interface CommentNodeState {
45 showRemoveDialog: boolean;
47 showBanDialog: boolean;
52 showConfirmTransferSite: boolean;
53 showConfirmTransferCommunity: boolean;
54 showConfirmAppointAsMod: boolean;
55 showConfirmAppointAsAdmin: boolean;
58 showAdvanced: boolean;
68 interface CommentNodeProps {
75 showContext?: boolean;
76 moderators: CommunityModeratorView[];
77 admins: UserViewSafe[];
78 // TODO is this necessary, can't I get it from the node itself?
79 postCreatorId?: number;
80 showCommunity?: boolean;
81 enableDownvotes: boolean;
84 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
85 private emptyState: CommentNodeState = {
88 showRemoveDialog: false,
94 banType: BanType.Community,
98 showConfirmTransferSite: false,
99 showConfirmTransferCommunity: false,
100 showConfirmAppointAsMod: false,
101 showConfirmAppointAsAdmin: false,
102 my_vote: this.props.node.comment_view.my_vote,
103 score: this.props.node.comment_view.counts.score,
104 upvotes: this.props.node.comment_view.counts.upvotes,
105 downvotes: this.props.node.comment_view.counts.downvotes,
106 borderColor: this.props.node.depth
107 ? colorList[this.props.node.depth % colorList.length]
113 constructor(props: any, context: any) {
114 super(props, context);
116 this.state = this.emptyState;
117 this.handleReplyCancel = this.handleReplyCancel.bind(this);
118 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
119 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
122 // TODO see if there's a better way to do this, and all willReceiveProps
123 componentWillReceiveProps(nextProps: CommentNodeProps) {
124 let cv = nextProps.node.comment_view;
125 this.state.my_vote = cv.my_vote;
126 this.state.upvotes = cv.counts.upvotes;
127 this.state.downvotes = cv.counts.downvotes;
128 this.state.score = cv.counts.score;
129 this.state.readLoading = false;
130 this.state.saveLoading = false;
131 this.setState(this.state);
135 let node = this.props.node;
136 let cv = this.props.node.comment_view;
139 className={`comment ${
140 cv.comment.parent_id && !this.props.noIndent ? "ml-1" : ""
144 id={`comment-${cv.comment.id}`}
145 className={`details comment-node py-2 ${
146 !this.props.noBorder ? "border-top border-light" : ""
147 } ${this.isCommentNew ? "mark" : ""}`}
149 !this.props.noIndent &&
150 cv.comment.parent_id &&
151 `border-left: 2px ${this.state.borderColor} solid !important`
155 class={`${!this.props.noIndent && cv.comment.parent_id && "ml-2"}`}
157 <div class="d-flex flex-wrap align-items-center text-muted small">
159 <UserListing user={cv.creator} />
163 <div className="badge badge-light d-none d-sm-inline mr-2">
168 <div className="badge badge-light d-none d-sm-inline mr-2">
172 {this.isPostCreator && (
173 <div className="badge badge-light d-none d-sm-inline mr-2">
177 {(cv.creator_banned_from_community || cv.creator.banned) && (
178 <div className="badge badge-danger mr-2">
182 {this.props.showCommunity && (
184 <span class="mx-1">{i18n.t("to")}</span>
185 <CommunityLink community={cv.community} />
186 <span class="mx-2">•</span>
187 <Link className="mr-2" to={`/post/${cv.post.id}`}>
193 class="btn btn-sm text-muted"
194 onClick={linkEvent(this, this.handleCommentCollapse)}
196 this.state.collapsed ? i18n.t("expand") : i18n.t("collapse")
199 {this.state.collapsed ? "+" : "—"}
201 {/* This is an expanding spacer for mobile */}
202 <div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
204 className={`unselectable pointer ${this.scoreColor}`}
205 onClick={linkEvent(node, this.handleCommentUpvote)}
206 data-tippy-content={this.pointsTippy}
209 class="mr-1 font-weight-bold"
210 aria-label={i18n.t("number_of_points", {
211 count: this.state.score,
217 <span className="mr-1">•</span>
219 <MomentTime data={cv.comment} />
222 {/* end of user row */}
223 {this.state.showEdit && (
227 onReplyCancel={this.handleReplyCancel}
228 disabled={this.props.locked}
232 {!this.state.showEdit && !this.state.collapsed && (
234 {this.state.viewSource ? (
235 <pre>{this.commentUnlessRemoved}</pre>
239 dangerouslySetInnerHTML={mdToHtml(
240 this.commentUnlessRemoved
244 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
245 {this.props.showContext && this.linkBtn}
246 {this.props.markable && (
248 class="btn btn-link btn-animate text-muted"
249 onClick={linkEvent(this, this.handleMarkRead)}
251 this.commentOrMentionRead
252 ? i18n.t("mark_as_unread")
253 : i18n.t("mark_as_read")
256 this.commentOrMentionRead
257 ? i18n.t("mark_as_unread")
258 : i18n.t("mark_as_read")
261 {this.state.readLoading ? (
266 classes={`icon-inline ${
267 this.commentOrMentionRead && "text-success"
273 {UserService.Instance.user && !this.props.viewOnly && (
276 className={`btn btn-link btn-animate ${
277 this.state.my_vote == 1 ? "text-info" : "text-muted"
279 onClick={linkEvent(node, this.handleCommentUpvote)}
280 data-tippy-content={i18n.t("upvote")}
281 aria-label={i18n.t("upvote")}
283 <Icon icon="arrow-up1" classes="icon-inline" />
284 {this.state.upvotes !== this.state.score && (
285 <span class="ml-1">{this.state.upvotes}</span>
288 {this.props.enableDownvotes && (
290 className={`btn btn-link btn-animate ${
291 this.state.my_vote == -1
295 onClick={linkEvent(node, this.handleCommentDownvote)}
296 data-tippy-content={i18n.t("downvote")}
297 aria-label={i18n.t("downvote")}
299 <Icon icon="arrow-down1" classes="icon-inline" />
300 {this.state.upvotes !== this.state.score && (
301 <span class="ml-1">{this.state.downvotes}</span>
306 class="btn btn-link btn-animate text-muted"
307 onClick={linkEvent(this, this.handleReplyClick)}
308 data-tippy-content={i18n.t("reply")}
309 aria-label={i18n.t("reply")}
311 <Icon icon="reply1" classes="icon-inline" />
313 {!this.state.showAdvanced ? (
315 className="btn btn-link btn-animate text-muted"
316 onClick={linkEvent(this, this.handleShowAdvanced)}
317 data-tippy-content={i18n.t("more")}
318 aria-label={i18n.t("more")}
320 <Icon icon="more-vertical" classes="icon-inline" />
324 {!this.myComment && (
325 <button class="btn btn-link btn-animate">
327 className="text-muted"
328 to={`/create_private_message/recipient/${cv.creator.id}`}
329 title={i18n.t("message").toLowerCase()}
335 {!this.props.showContext && this.linkBtn}
337 class="btn btn-link btn-animate text-muted"
340 this.handleSaveCommentClick
343 cv.saved ? i18n.t("unsave") : i18n.t("save")
346 cv.saved ? i18n.t("unsave") : i18n.t("save")
349 {this.state.saveLoading ? (
354 classes={`icon-inline ${
355 cv.saved && "text-warning"
361 className="btn btn-link btn-animate text-muted"
362 onClick={linkEvent(this, this.handleViewSource)}
363 data-tippy-content={i18n.t("view_source")}
364 aria-label={i18n.t("view_source")}
368 classes={`icon-inline ${
369 this.state.viewSource && "text-success"
376 class="btn btn-link btn-animate text-muted"
377 onClick={linkEvent(this, this.handleEditClick)}
378 data-tippy-content={i18n.t("edit")}
379 aria-label={i18n.t("edit")}
381 <Icon icon="edit" classes="icon-inline" />
384 class="btn btn-link btn-animate text-muted"
387 this.handleDeleteClick
402 classes={`icon-inline ${
403 cv.comment.deleted && "text-danger"
409 {/* Admins and mods can remove comments */}
410 {(this.canMod || this.canAdmin) && (
412 {!cv.comment.removed ? (
414 class="btn btn-link btn-animate text-muted"
417 this.handleModRemoveShow
419 aria-label={i18n.t("remove")}
425 class="btn btn-link btn-animate text-muted"
428 this.handleModRemoveSubmit
430 aria-label={i18n.t("restore")}
437 {/* Mods can ban from community, and appoint as mods to community */}
441 (!cv.creator_banned_from_community ? (
443 class="btn btn-link btn-animate text-muted"
446 this.handleModBanFromCommunityShow
448 aria-label={i18n.t("ban")}
454 class="btn btn-link btn-animate text-muted"
457 this.handleModBanFromCommunitySubmit
459 aria-label={i18n.t("unban")}
464 {!cv.creator_banned_from_community &&
466 (!this.state.showConfirmAppointAsMod ? (
468 class="btn btn-link btn-animate text-muted"
471 this.handleShowConfirmAppointAsMod
475 ? i18n.t("remove_as_mod")
476 : i18n.t("appoint_as_mod")
480 ? i18n.t("remove_as_mod")
481 : i18n.t("appoint_as_mod")}
486 class="btn btn-link btn-animate text-muted"
487 aria-label={i18n.t("are_you_sure")}
489 {i18n.t("are_you_sure")}
492 class="btn btn-link btn-animate text-muted"
495 this.handleAddModToCommunity
497 aria-label={i18n.t("yes")}
502 class="btn btn-link btn-animate text-muted"
505 this.handleCancelConfirmAppointAsMod
507 aria-label={i18n.t("no")}
515 {/* Community creators and admins can transfer community to another mod */}
516 {(this.amCommunityCreator || this.canAdmin) &&
519 (!this.state.showConfirmTransferCommunity ? (
521 class="btn btn-link btn-animate text-muted"
524 this.handleShowConfirmTransferCommunity
526 aria-label={i18n.t("transfer_community")}
528 {i18n.t("transfer_community")}
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.handleTransferCommunity
544 aria-label={i18n.t("yes")}
549 class="btn btn-link btn-animate text-muted"
553 .handleCancelShowConfirmTransferCommunity
555 aria-label={i18n.t("no")}
561 {/* Admins can ban from all, and appoint other admins */}
565 (!cv.creator.banned ? (
567 class="btn btn-link btn-animate text-muted"
570 this.handleModBanShow
572 aria-label={i18n.t("ban_from_site")}
574 {i18n.t("ban_from_site")}
578 class="btn btn-link btn-animate text-muted"
581 this.handleModBanSubmit
583 aria-label={i18n.t("unban_from_site")}
585 {i18n.t("unban_from_site")}
588 {!cv.creator.banned &&
590 (!this.state.showConfirmAppointAsAdmin ? (
592 class="btn btn-link btn-animate text-muted"
595 this.handleShowConfirmAppointAsAdmin
599 ? i18n.t("remove_as_admin")
600 : i18n.t("appoint_as_admin")
604 ? i18n.t("remove_as_admin")
605 : i18n.t("appoint_as_admin")}
609 <button class="btn btn-link btn-animate text-muted">
610 {i18n.t("are_you_sure")}
613 class="btn btn-link btn-animate text-muted"
618 aria-label={i18n.t("yes")}
623 class="btn btn-link btn-animate text-muted"
626 this.handleCancelConfirmAppointAsAdmin
628 aria-label={i18n.t("no")}
636 {/* Site Creator can transfer to another admin */}
637 {this.amSiteCreator &&
640 (!this.state.showConfirmTransferSite ? (
642 class="btn btn-link btn-animate text-muted"
645 this.handleShowConfirmTransferSite
647 aria-label={i18n.t("transfer_site")}
649 {i18n.t("transfer_site")}
654 class="btn btn-link btn-animate text-muted"
655 aria-label={i18n.t("are_you_sure")}
657 {i18n.t("are_you_sure")}
660 class="btn btn-link btn-animate text-muted"
663 this.handleTransferSite
665 aria-label={i18n.t("yes")}
670 class="btn btn-link btn-animate text-muted"
673 this.handleCancelShowConfirmTransferSite
675 aria-label={i18n.t("no")}
686 {/* end of button group */}
691 {/* end of details */}
692 {this.state.showRemoveDialog && (
695 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
699 htmlFor={`mod-remove-reason-${cv.comment.id}`}
705 id={`mod-remove-reason-${cv.comment.id}`}
706 class="form-control mr-2"
707 placeholder={i18n.t("reason")}
708 value={this.state.removeReason}
709 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
713 class="btn btn-secondary"
714 aria-label={i18n.t("remove_comment")}
716 {i18n.t("remove_comment")}
720 {this.state.showBanDialog && (
721 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
722 <div class="form-group row">
724 class="col-form-label"
725 htmlFor={`mod-ban-reason-${cv.comment.id}`}
731 id={`mod-ban-reason-${cv.comment.id}`}
732 class="form-control mr-2"
733 placeholder={i18n.t("reason")}
734 value={this.state.banReason}
735 onInput={linkEvent(this, this.handleModBanReasonChange)}
737 <div class="form-group">
738 <div class="form-check">
740 class="form-check-input"
741 id="mod-ban-remove-data"
743 checked={this.state.removeData}
744 onChange={linkEvent(this, this.handleModRemoveDataChange)}
746 <label class="form-check-label" htmlFor="mod-ban-remove-data">
747 {i18n.t("remove_posts_comments")}
752 {/* TODO hold off on expires until later */}
753 {/* <div class="form-group row"> */}
754 {/* <label class="col-form-label">Expires</label> */}
755 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
757 <div class="form-group row">
760 class="btn btn-secondary"
761 aria-label={i18n.t("ban")}
763 {i18n.t("ban")} {cv.creator.name}
768 {this.state.showReply && (
771 onReplyCancel={this.handleReplyCancel}
772 disabled={this.props.locked}
776 {node.children && !this.state.collapsed && (
778 nodes={node.children}
779 locked={this.props.locked}
780 moderators={this.props.moderators}
781 admins={this.props.admins}
782 postCreatorId={this.props.postCreatorId}
783 enableDownvotes={this.props.enableDownvotes}
786 {/* A collapsed clearfix */}
787 {this.state.collapsed && <div class="row col-12"></div>}
792 get commentOrMentionRead() {
793 let cv = this.props.node.comment_view;
794 return this.isUserMentionType(cv) ? cv.user_mention.read : cv.comment.read;
798 let cv = this.props.node.comment_view;
801 className="btn btn-link btn-animate text-muted"
802 to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
803 title={this.props.showContext ? i18n.t("show_context") : i18n.t("link")}
805 <Icon icon="link" classes="icon-inline" />
814 get myComment(): boolean {
816 UserService.Instance.user &&
817 this.props.node.comment_view.creator.id == UserService.Instance.user.id
821 get isMod(): boolean {
823 this.props.moderators &&
825 this.props.moderators.map(m => m.moderator.id),
826 this.props.node.comment_view.creator.id
831 get isAdmin(): boolean {
835 this.props.admins.map(a => a.user.id),
836 this.props.node.comment_view.creator.id
841 get isPostCreator(): boolean {
842 return this.props.node.comment_view.creator.id == this.props.postCreatorId;
845 get canMod(): boolean {
846 if (this.props.admins && this.props.moderators) {
847 let adminsThenMods = this.props.admins
849 .concat(this.props.moderators.map(m => m.moderator.id));
852 UserService.Instance.user,
854 this.props.node.comment_view.creator.id
861 get canAdmin(): boolean {
865 UserService.Instance.user,
866 this.props.admins.map(a => a.user.id),
867 this.props.node.comment_view.creator.id
872 get amCommunityCreator(): boolean {
874 this.props.moderators &&
875 UserService.Instance.user &&
876 this.props.node.comment_view.creator.id != UserService.Instance.user.id &&
877 UserService.Instance.user.id == this.props.moderators[0].moderator.id
881 get amSiteCreator(): boolean {
884 UserService.Instance.user &&
885 this.props.node.comment_view.creator.id != UserService.Instance.user.id &&
886 UserService.Instance.user.id == this.props.admins[0].user.id
890 get commentUnlessRemoved(): string {
891 let comment = this.props.node.comment_view.comment;
892 return comment.removed
893 ? `*${i18n.t("removed")}*`
895 ? `*${i18n.t("deleted")}*`
899 handleReplyClick(i: CommentNode) {
900 i.state.showReply = true;
904 handleEditClick(i: CommentNode) {
905 i.state.showEdit = true;
909 handleDeleteClick(i: CommentNode) {
910 let comment = i.props.node.comment_view.comment;
911 let deleteForm: DeleteComment = {
912 comment_id: comment.id,
913 deleted: !comment.deleted,
916 WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
919 handleSaveCommentClick(i: CommentNode) {
920 let cv = i.props.node.comment_view;
921 let save = cv.saved == undefined ? true : !cv.saved;
922 let form: SaveComment = {
923 comment_id: cv.comment.id,
928 WebSocketService.Instance.send(wsClient.saveComment(form));
930 i.state.saveLoading = true;
931 i.setState(this.state);
934 handleReplyCancel() {
935 this.state.showReply = false;
936 this.state.showEdit = false;
937 this.setState(this.state);
940 handleCommentUpvote(i: CommentNodeI, event: any) {
941 event.preventDefault();
942 let new_vote = this.state.my_vote == 1 ? 0 : 1;
944 if (this.state.my_vote == 1) {
946 this.state.upvotes--;
947 } else if (this.state.my_vote == -1) {
948 this.state.downvotes--;
949 this.state.upvotes++;
950 this.state.score += 2;
952 this.state.upvotes++;
956 this.state.my_vote = new_vote;
958 let form: CreateCommentLike = {
959 comment_id: i.comment_view.comment.id,
960 score: this.state.my_vote,
964 WebSocketService.Instance.send(wsClient.likeComment(form));
965 this.setState(this.state);
969 handleCommentDownvote(i: CommentNodeI, event: any) {
970 event.preventDefault();
971 let new_vote = this.state.my_vote == -1 ? 0 : -1;
973 if (this.state.my_vote == 1) {
974 this.state.score -= 2;
975 this.state.upvotes--;
976 this.state.downvotes++;
977 } else if (this.state.my_vote == -1) {
978 this.state.downvotes--;
981 this.state.downvotes++;
985 this.state.my_vote = new_vote;
987 let form: CreateCommentLike = {
988 comment_id: i.comment_view.comment.id,
989 score: this.state.my_vote,
993 WebSocketService.Instance.send(wsClient.likeComment(form));
994 this.setState(this.state);
998 handleModRemoveShow(i: CommentNode) {
999 i.state.showRemoveDialog = true;
1000 i.setState(i.state);
1003 handleModRemoveReasonChange(i: CommentNode, event: any) {
1004 i.state.removeReason = event.target.value;
1005 i.setState(i.state);
1008 handleModRemoveDataChange(i: CommentNode, event: any) {
1009 i.state.removeData = event.target.checked;
1010 i.setState(i.state);
1013 handleModRemoveSubmit(i: CommentNode) {
1014 let comment = i.props.node.comment_view.comment;
1015 let form: RemoveComment = {
1016 comment_id: comment.id,
1017 removed: !comment.removed,
1018 reason: i.state.removeReason,
1021 WebSocketService.Instance.send(wsClient.removeComment(form));
1023 i.state.showRemoveDialog = false;
1024 i.setState(i.state);
1028 item: CommentView | UserMentionView
1029 ): item is UserMentionView {
1030 return (item as UserMentionView).user_mention?.id !== undefined;
1033 handleMarkRead(i: CommentNode) {
1034 if (i.isUserMentionType(i.props.node.comment_view)) {
1035 let form: MarkUserMentionAsRead = {
1036 user_mention_id: i.props.node.comment_view.user_mention.id,
1037 read: !i.props.node.comment_view.user_mention.read,
1040 WebSocketService.Instance.send(wsClient.markUserMentionAsRead(form));
1042 let form: MarkCommentAsRead = {
1043 comment_id: i.props.node.comment_view.comment.id,
1044 read: !i.props.node.comment_view.comment.read,
1047 WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
1050 i.state.readLoading = true;
1051 i.setState(this.state);
1054 handleModBanFromCommunityShow(i: CommentNode) {
1055 i.state.showBanDialog = !i.state.showBanDialog;
1056 i.state.banType = BanType.Community;
1057 i.setState(i.state);
1060 handleModBanShow(i: CommentNode) {
1061 i.state.showBanDialog = !i.state.showBanDialog;
1062 i.state.banType = BanType.Site;
1063 i.setState(i.state);
1066 handleModBanReasonChange(i: CommentNode, event: any) {
1067 i.state.banReason = event.target.value;
1068 i.setState(i.state);
1071 handleModBanExpiresChange(i: CommentNode, event: any) {
1072 i.state.banExpires = event.target.value;
1073 i.setState(i.state);
1076 handleModBanFromCommunitySubmit(i: CommentNode) {
1077 i.state.banType = BanType.Community;
1078 i.setState(i.state);
1079 i.handleModBanBothSubmit(i);
1082 handleModBanSubmit(i: CommentNode) {
1083 i.state.banType = BanType.Site;
1084 i.setState(i.state);
1085 i.handleModBanBothSubmit(i);
1088 handleModBanBothSubmit(i: CommentNode) {
1089 let cv = i.props.node.comment_view;
1091 if (i.state.banType == BanType.Community) {
1092 // If its an unban, restore all their data
1093 let ban = !cv.creator_banned_from_community;
1095 i.state.removeData = false;
1097 let form: BanFromCommunity = {
1098 user_id: cv.creator.id,
1099 community_id: cv.community.id,
1101 remove_data: i.state.removeData,
1102 reason: i.state.banReason,
1103 expires: getUnixTime(i.state.banExpires),
1106 WebSocketService.Instance.send(wsClient.banFromCommunity(form));
1108 // If its an unban, restore all their data
1109 let ban = !cv.creator.banned;
1111 i.state.removeData = false;
1113 let form: BanUser = {
1114 user_id: cv.creator.id,
1116 remove_data: i.state.removeData,
1117 reason: i.state.banReason,
1118 expires: getUnixTime(i.state.banExpires),
1121 WebSocketService.Instance.send(wsClient.banUser(form));
1124 i.state.showBanDialog = false;
1125 i.setState(i.state);
1128 handleShowConfirmAppointAsMod(i: CommentNode) {
1129 i.state.showConfirmAppointAsMod = true;
1130 i.setState(i.state);
1133 handleCancelConfirmAppointAsMod(i: CommentNode) {
1134 i.state.showConfirmAppointAsMod = false;
1135 i.setState(i.state);
1138 handleAddModToCommunity(i: CommentNode) {
1139 let cv = i.props.node.comment_view;
1140 let form: AddModToCommunity = {
1141 user_id: cv.creator.id,
1142 community_id: cv.community.id,
1146 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
1147 i.state.showConfirmAppointAsMod = false;
1148 i.setState(i.state);
1151 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1152 i.state.showConfirmAppointAsAdmin = true;
1153 i.setState(i.state);
1156 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1157 i.state.showConfirmAppointAsAdmin = false;
1158 i.setState(i.state);
1161 handleAddAdmin(i: CommentNode) {
1162 let form: AddAdmin = {
1163 user_id: i.props.node.comment_view.creator.id,
1167 WebSocketService.Instance.send(wsClient.addAdmin(form));
1168 i.state.showConfirmAppointAsAdmin = false;
1169 i.setState(i.state);
1172 handleShowConfirmTransferCommunity(i: CommentNode) {
1173 i.state.showConfirmTransferCommunity = true;
1174 i.setState(i.state);
1177 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1178 i.state.showConfirmTransferCommunity = false;
1179 i.setState(i.state);
1182 handleTransferCommunity(i: CommentNode) {
1183 let cv = i.props.node.comment_view;
1184 let form: TransferCommunity = {
1185 community_id: cv.community.id,
1186 user_id: cv.creator.id,
1189 WebSocketService.Instance.send(wsClient.transferCommunity(form));
1190 i.state.showConfirmTransferCommunity = false;
1191 i.setState(i.state);
1194 handleShowConfirmTransferSite(i: CommentNode) {
1195 i.state.showConfirmTransferSite = true;
1196 i.setState(i.state);
1199 handleCancelShowConfirmTransferSite(i: CommentNode) {
1200 i.state.showConfirmTransferSite = false;
1201 i.setState(i.state);
1204 handleTransferSite(i: CommentNode) {
1205 let form: TransferSite = {
1206 user_id: i.props.node.comment_view.creator.id,
1209 WebSocketService.Instance.send(wsClient.transferSite(form));
1210 i.state.showConfirmTransferSite = false;
1211 i.setState(i.state);
1214 get isCommentNew(): boolean {
1215 let now = moment.utc().subtract(10, "minutes");
1216 let then = moment.utc(this.props.node.comment_view.comment.published);
1217 return now.isBefore(then);
1220 handleCommentCollapse(i: CommentNode) {
1221 i.state.collapsed = !i.state.collapsed;
1222 i.setState(i.state);
1225 handleViewSource(i: CommentNode) {
1226 i.state.viewSource = !i.state.viewSource;
1227 i.setState(i.state);
1230 handleShowAdvanced(i: CommentNode) {
1231 i.state.showAdvanced = !i.state.showAdvanced;
1232 i.setState(i.state);
1237 if (this.state.my_vote == 1) {
1239 } else if (this.state.my_vote == -1) {
1240 return "text-danger";
1242 return "text-muted";
1246 get pointsTippy(): string {
1247 let points = i18n.t("number_of_points", {
1248 count: this.state.score,
1251 let upvotes = i18n.t("number_of_upvotes", {
1252 count: this.state.upvotes,
1255 let downvotes = i18n.t("number_of_downvotes", {
1256 count: this.state.downvotes,
1259 return `${points} • ${upvotes} • ${downvotes}`;