1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
9 CommunityModeratorView,
13 MarkPersonMentionAsRead,
20 } from "lemmy-js-client";
21 import moment from "moment";
22 import { i18n } from "../../i18next";
23 import { BanType, CommentNode as CommentNodeI } from "../../interfaces";
24 import { UserService, WebSocketService } from "../../services";
36 import { Icon, Spinner } from "../common/icon";
37 import { MomentTime } from "../common/moment-time";
38 import { CommunityLink } from "../community/community-link";
39 import { PersonListing } from "../person/person-listing";
40 import { CommentForm } from "./comment-form";
41 import { CommentNodes } from "./comment-nodes";
43 interface CommentNodeState {
46 showRemoveDialog: boolean;
48 showBanDialog: boolean;
53 showConfirmTransferSite: boolean;
54 showConfirmTransferCommunity: boolean;
55 showConfirmAppointAsMod: boolean;
56 showConfirmAppointAsAdmin: boolean;
59 showAdvanced: boolean;
69 interface CommentNodeProps {
76 showContext?: boolean;
77 moderators: CommunityModeratorView[];
78 admins: PersonViewSafe[];
79 // TODO is this necessary, can't I get it from the node itself?
80 postCreatorId?: number;
81 showCommunity?: boolean;
82 enableDownvotes: boolean;
85 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
86 private emptyState: CommentNodeState = {
89 showRemoveDialog: false,
95 banType: BanType.Community,
99 showConfirmTransferSite: false,
100 showConfirmTransferCommunity: false,
101 showConfirmAppointAsMod: false,
102 showConfirmAppointAsAdmin: false,
103 my_vote: this.props.node.comment_view.my_vote,
104 score: this.props.node.comment_view.counts.score,
105 upvotes: this.props.node.comment_view.counts.upvotes,
106 downvotes: this.props.node.comment_view.counts.downvotes,
107 borderColor: this.props.node.depth
108 ? colorList[this.props.node.depth % colorList.length]
114 constructor(props: any, context: any) {
115 super(props, context);
117 this.state = this.emptyState;
118 this.handleReplyCancel = this.handleReplyCancel.bind(this);
119 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
120 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
123 // TODO see if there's a better way to do this, and all willReceiveProps
124 componentWillReceiveProps(nextProps: CommentNodeProps) {
125 let cv = nextProps.node.comment_view;
126 this.state.my_vote = cv.my_vote;
127 this.state.upvotes = cv.counts.upvotes;
128 this.state.downvotes = cv.counts.downvotes;
129 this.state.score = cv.counts.score;
130 this.state.readLoading = false;
131 this.state.saveLoading = false;
132 this.setState(this.state);
136 let node = this.props.node;
137 let cv = this.props.node.comment_view;
140 className={`comment ${
141 cv.comment.parent_id && !this.props.noIndent ? "ml-1" : ""
145 id={`comment-${cv.comment.id}`}
146 className={`details comment-node py-2 ${
147 !this.props.noBorder ? "border-top border-light" : ""
148 } ${this.isCommentNew ? "mark" : ""}`}
150 !this.props.noIndent &&
151 cv.comment.parent_id &&
152 `border-left: 2px ${this.state.borderColor} solid !important`
156 class={`${!this.props.noIndent && cv.comment.parent_id && "ml-2"}`}
158 <div class="d-flex flex-wrap align-items-center text-muted small">
160 <PersonListing person={cv.creator} />
164 <div className="badge badge-light d-none d-sm-inline mr-2">
169 <div className="badge badge-light d-none d-sm-inline mr-2">
173 {this.isPostCreator && (
174 <div className="badge badge-light d-none d-sm-inline mr-2">
178 {(cv.creator_banned_from_community || cv.creator.banned) && (
179 <div className="badge badge-danger mr-2">
183 {this.props.showCommunity && (
185 <span class="mx-1">{i18n.t("to")}</span>
186 <CommunityLink community={cv.community} />
187 <span class="mx-2">•</span>
188 <Link className="mr-2" to={`/post/${cv.post.id}`}>
194 class="btn btn-sm text-muted"
195 onClick={linkEvent(this, this.handleCommentCollapse)}
196 aria-label={this.expandText}
197 data-tippy-content={this.expandText}
199 {this.state.collapsed ? (
200 <Icon icon="plus-square" classes="icon-inline" />
202 <Icon icon="minus-square" classes="icon-inline" />
206 {/* This is an expanding spacer for mobile */}
207 <div className="mr-lg-5 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
211 className={`unselectable pointer ${this.scoreColor}`}
212 onClick={linkEvent(node, this.handleCommentUpvote)}
213 data-tippy-content={this.pointsTippy}
216 class="mr-1 font-weight-bold"
217 aria-label={i18n.t("number_of_points", {
218 count: this.state.score,
224 <span className="mr-1">•</span>
228 <MomentTime data={cv.comment} />
231 {/* end of user row */}
232 {this.state.showEdit && (
236 onReplyCancel={this.handleReplyCancel}
237 disabled={this.props.locked}
241 {!this.state.showEdit && !this.state.collapsed && (
243 {this.state.viewSource ? (
244 <pre>{this.commentUnlessRemoved}</pre>
248 dangerouslySetInnerHTML={mdToHtml(
249 this.commentUnlessRemoved
253 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
254 {this.props.showContext && this.linkBtn()}
255 {this.props.markable && (
257 class="btn btn-link btn-animate text-muted"
258 onClick={linkEvent(this, this.handleMarkRead)}
260 this.commentOrMentionRead
261 ? i18n.t("mark_as_unread")
262 : i18n.t("mark_as_read")
265 this.commentOrMentionRead
266 ? i18n.t("mark_as_unread")
267 : i18n.t("mark_as_read")
270 {this.state.readLoading ? (
275 classes={`icon-inline ${
276 this.commentOrMentionRead && "text-success"
282 {UserService.Instance.localUserView && !this.props.viewOnly && (
285 className={`btn btn-link btn-animate ${
286 this.state.my_vote == 1 ? "text-info" : "text-muted"
288 onClick={linkEvent(node, this.handleCommentUpvote)}
289 data-tippy-content={i18n.t("upvote")}
290 aria-label={i18n.t("upvote")}
292 <Icon icon="arrow-up1" classes="icon-inline" />
294 this.state.upvotes !== this.state.score && (
295 <span class="ml-1">{this.state.upvotes}</span>
298 {this.props.enableDownvotes && (
300 className={`btn btn-link btn-animate ${
301 this.state.my_vote == -1
305 onClick={linkEvent(node, this.handleCommentDownvote)}
306 data-tippy-content={i18n.t("downvote")}
307 aria-label={i18n.t("downvote")}
309 <Icon icon="arrow-down1" classes="icon-inline" />
311 this.state.upvotes !== this.state.score && (
312 <span class="ml-1">{this.state.downvotes}</span>
317 class="btn btn-link btn-animate text-muted"
318 onClick={linkEvent(this, this.handleReplyClick)}
319 data-tippy-content={i18n.t("reply")}
320 aria-label={i18n.t("reply")}
322 <Icon icon="reply1" classes="icon-inline" />
324 {!this.state.showAdvanced ? (
326 className="btn btn-link btn-animate text-muted"
327 onClick={linkEvent(this, this.handleShowAdvanced)}
328 data-tippy-content={i18n.t("more")}
329 aria-label={i18n.t("more")}
331 <Icon icon="more-vertical" classes="icon-inline" />
335 {!this.myComment && (
336 <button class="btn btn-link btn-animate">
338 className="text-muted"
339 to={`/create_private_message/recipient/${cv.creator.id}`}
340 title={i18n.t("message").toLowerCase()}
347 class="btn btn-link btn-animate text-muted"
350 this.handleSaveCommentClick
353 cv.saved ? i18n.t("unsave") : i18n.t("save")
356 cv.saved ? i18n.t("unsave") : i18n.t("save")
359 {this.state.saveLoading ? (
364 classes={`icon-inline ${
365 cv.saved && "text-warning"
371 className="btn btn-link btn-animate text-muted"
372 onClick={linkEvent(this, this.handleViewSource)}
373 data-tippy-content={i18n.t("view_source")}
374 aria-label={i18n.t("view_source")}
378 classes={`icon-inline ${
379 this.state.viewSource && "text-success"
386 class="btn btn-link btn-animate text-muted"
387 onClick={linkEvent(this, this.handleEditClick)}
388 data-tippy-content={i18n.t("edit")}
389 aria-label={i18n.t("edit")}
391 <Icon icon="edit" classes="icon-inline" />
394 class="btn btn-link btn-animate text-muted"
397 this.handleDeleteClick
412 classes={`icon-inline ${
413 cv.comment.deleted && "text-danger"
419 {/* Admins and mods can remove comments */}
420 {(this.canMod || this.canAdmin) && (
422 {!cv.comment.removed ? (
424 class="btn btn-link btn-animate text-muted"
427 this.handleModRemoveShow
429 aria-label={i18n.t("remove")}
435 class="btn btn-link btn-animate text-muted"
438 this.handleModRemoveSubmit
440 aria-label={i18n.t("restore")}
447 {/* Mods can ban from community, and appoint as mods to community */}
451 (!cv.creator_banned_from_community ? (
453 class="btn btn-link btn-animate text-muted"
456 this.handleModBanFromCommunityShow
458 aria-label={i18n.t("ban")}
464 class="btn btn-link btn-animate text-muted"
467 this.handleModBanFromCommunitySubmit
469 aria-label={i18n.t("unban")}
474 {!cv.creator_banned_from_community &&
475 (!this.state.showConfirmAppointAsMod ? (
477 class="btn btn-link btn-animate text-muted"
480 this.handleShowConfirmAppointAsMod
484 ? i18n.t("remove_as_mod")
485 : i18n.t("appoint_as_mod")
489 ? i18n.t("remove_as_mod")
490 : i18n.t("appoint_as_mod")}
495 class="btn btn-link btn-animate text-muted"
496 aria-label={i18n.t("are_you_sure")}
498 {i18n.t("are_you_sure")}
501 class="btn btn-link btn-animate text-muted"
504 this.handleAddModToCommunity
506 aria-label={i18n.t("yes")}
511 class="btn btn-link btn-animate text-muted"
514 this.handleCancelConfirmAppointAsMod
516 aria-label={i18n.t("no")}
524 {/* Community creators and admins can transfer community to another mod */}
525 {(this.amCommunityCreator || this.canAdmin) &&
528 (!this.state.showConfirmTransferCommunity ? (
530 class="btn btn-link btn-animate text-muted"
533 this.handleShowConfirmTransferCommunity
535 aria-label={i18n.t("transfer_community")}
537 {i18n.t("transfer_community")}
542 class="btn btn-link btn-animate text-muted"
543 aria-label={i18n.t("are_you_sure")}
545 {i18n.t("are_you_sure")}
548 class="btn btn-link btn-animate text-muted"
551 this.handleTransferCommunity
553 aria-label={i18n.t("yes")}
558 class="btn btn-link btn-animate text-muted"
562 .handleCancelShowConfirmTransferCommunity
564 aria-label={i18n.t("no")}
570 {/* Admins can ban from all, and appoint other admins */}
574 (!cv.creator.banned ? (
576 class="btn btn-link btn-animate text-muted"
579 this.handleModBanShow
581 aria-label={i18n.t("ban_from_site")}
583 {i18n.t("ban_from_site")}
587 class="btn btn-link btn-animate text-muted"
590 this.handleModBanSubmit
592 aria-label={i18n.t("unban_from_site")}
594 {i18n.t("unban_from_site")}
597 {!cv.creator.banned &&
599 (!this.state.showConfirmAppointAsAdmin ? (
601 class="btn btn-link btn-animate text-muted"
604 this.handleShowConfirmAppointAsAdmin
608 ? i18n.t("remove_as_admin")
609 : i18n.t("appoint_as_admin")
613 ? i18n.t("remove_as_admin")
614 : i18n.t("appoint_as_admin")}
618 <button class="btn btn-link btn-animate text-muted">
619 {i18n.t("are_you_sure")}
622 class="btn btn-link btn-animate text-muted"
627 aria-label={i18n.t("yes")}
632 class="btn btn-link btn-animate text-muted"
635 this.handleCancelConfirmAppointAsAdmin
637 aria-label={i18n.t("no")}
645 {/* Site Creator can transfer to another admin */}
646 {this.amSiteCreator &&
649 (!this.state.showConfirmTransferSite ? (
651 class="btn btn-link btn-animate text-muted"
654 this.handleShowConfirmTransferSite
656 aria-label={i18n.t("transfer_site")}
658 {i18n.t("transfer_site")}
663 class="btn btn-link btn-animate text-muted"
664 aria-label={i18n.t("are_you_sure")}
666 {i18n.t("are_you_sure")}
669 class="btn btn-link btn-animate text-muted"
672 this.handleTransferSite
674 aria-label={i18n.t("yes")}
679 class="btn btn-link btn-animate text-muted"
682 this.handleCancelShowConfirmTransferSite
684 aria-label={i18n.t("no")}
695 {/* end of button group */}
700 {/* end of details */}
701 {this.state.showRemoveDialog && (
704 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
708 htmlFor={`mod-remove-reason-${cv.comment.id}`}
714 id={`mod-remove-reason-${cv.comment.id}`}
715 class="form-control mr-2"
716 placeholder={i18n.t("reason")}
717 value={this.state.removeReason}
718 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
722 class="btn btn-secondary"
723 aria-label={i18n.t("remove_comment")}
725 {i18n.t("remove_comment")}
729 {this.state.showBanDialog && (
730 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
731 <div class="form-group row">
733 class="col-form-label"
734 htmlFor={`mod-ban-reason-${cv.comment.id}`}
740 id={`mod-ban-reason-${cv.comment.id}`}
741 class="form-control mr-2"
742 placeholder={i18n.t("reason")}
743 value={this.state.banReason}
744 onInput={linkEvent(this, this.handleModBanReasonChange)}
746 <div class="form-group">
747 <div class="form-check">
749 class="form-check-input"
750 id="mod-ban-remove-data"
752 checked={this.state.removeData}
753 onChange={linkEvent(this, this.handleModRemoveDataChange)}
755 <label class="form-check-label" htmlFor="mod-ban-remove-data">
756 {i18n.t("remove_posts_comments")}
761 {/* TODO hold off on expires until later */}
762 {/* <div class="form-group row"> */}
763 {/* <label class="col-form-label">Expires</label> */}
764 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
766 <div class="form-group row">
769 class="btn btn-secondary"
770 aria-label={i18n.t("ban")}
772 {i18n.t("ban")} {cv.creator.name}
777 {this.state.showReply && (
780 onReplyCancel={this.handleReplyCancel}
781 disabled={this.props.locked}
785 {node.children && !this.state.collapsed && (
787 nodes={node.children}
788 locked={this.props.locked}
789 moderators={this.props.moderators}
790 admins={this.props.admins}
791 postCreatorId={this.props.postCreatorId}
792 enableDownvotes={this.props.enableDownvotes}
795 {/* A collapsed clearfix */}
796 {this.state.collapsed && <div class="row col-12"></div>}
801 get commentOrMentionRead() {
802 let cv = this.props.node.comment_view;
803 return this.isPersonMentionType(cv)
804 ? cv.person_mention.read
808 linkBtn(small = false) {
809 let cv = this.props.node.comment_view;
812 className={`btn ${small && "btn-sm"} btn-link btn-animate text-muted`}
813 to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
814 title={this.props.showContext ? i18n.t("show_context") : i18n.t("link")}
816 <Icon icon="link" classes="icon-inline" />
825 get myComment(): boolean {
827 this.props.node.comment_view.creator.id ==
828 UserService.Instance.localUserView?.person.id
832 get isMod(): boolean {
834 this.props.moderators &&
836 this.props.moderators.map(m => m.moderator.id),
837 this.props.node.comment_view.creator.id
842 get isAdmin(): boolean {
846 this.props.admins.map(a => a.person.id),
847 this.props.node.comment_view.creator.id
852 get isPostCreator(): boolean {
853 return this.props.node.comment_view.creator.id == this.props.postCreatorId;
856 get canMod(): boolean {
857 if (this.props.admins && this.props.moderators) {
858 let adminsThenMods = this.props.admins
859 .map(a => a.person.id)
860 .concat(this.props.moderators.map(m => m.moderator.id));
863 UserService.Instance.localUserView,
865 this.props.node.comment_view.creator.id
872 get canAdmin(): boolean {
876 UserService.Instance.localUserView,
877 this.props.admins.map(a => a.person.id),
878 this.props.node.comment_view.creator.id
883 get amCommunityCreator(): boolean {
885 this.props.moderators &&
886 UserService.Instance.localUserView &&
887 this.props.node.comment_view.creator.id !=
888 UserService.Instance.localUserView.person.id &&
889 UserService.Instance.localUserView.person.id ==
890 this.props.moderators[0].moderator.id
894 get amSiteCreator(): boolean {
897 UserService.Instance.localUserView &&
898 this.props.node.comment_view.creator.id !=
899 UserService.Instance.localUserView.person.id &&
900 UserService.Instance.localUserView.person.id ==
901 this.props.admins[0].person.id
905 get commentUnlessRemoved(): string {
906 let comment = this.props.node.comment_view.comment;
907 return comment.removed
908 ? `*${i18n.t("removed")}*`
910 ? `*${i18n.t("deleted")}*`
914 handleReplyClick(i: CommentNode) {
915 i.state.showReply = true;
919 handleEditClick(i: CommentNode) {
920 i.state.showEdit = true;
924 handleDeleteClick(i: CommentNode) {
925 let comment = i.props.node.comment_view.comment;
926 let deleteForm: DeleteComment = {
927 comment_id: comment.id,
928 deleted: !comment.deleted,
931 WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
934 handleSaveCommentClick(i: CommentNode) {
935 let cv = i.props.node.comment_view;
936 let save = cv.saved == undefined ? true : !cv.saved;
937 let form: SaveComment = {
938 comment_id: cv.comment.id,
943 WebSocketService.Instance.send(wsClient.saveComment(form));
945 i.state.saveLoading = true;
946 i.setState(this.state);
949 handleReplyCancel() {
950 this.state.showReply = false;
951 this.state.showEdit = false;
952 this.setState(this.state);
955 handleCommentUpvote(i: CommentNodeI, event: any) {
956 event.preventDefault();
957 let new_vote = this.state.my_vote == 1 ? 0 : 1;
959 if (this.state.my_vote == 1) {
961 this.state.upvotes--;
962 } else if (this.state.my_vote == -1) {
963 this.state.downvotes--;
964 this.state.upvotes++;
965 this.state.score += 2;
967 this.state.upvotes++;
971 this.state.my_vote = new_vote;
973 let form: CreateCommentLike = {
974 comment_id: i.comment_view.comment.id,
975 score: this.state.my_vote,
979 WebSocketService.Instance.send(wsClient.likeComment(form));
980 this.setState(this.state);
984 handleCommentDownvote(i: CommentNodeI, event: any) {
985 event.preventDefault();
986 let new_vote = this.state.my_vote == -1 ? 0 : -1;
988 if (this.state.my_vote == 1) {
989 this.state.score -= 2;
990 this.state.upvotes--;
991 this.state.downvotes++;
992 } else if (this.state.my_vote == -1) {
993 this.state.downvotes--;
996 this.state.downvotes++;
1000 this.state.my_vote = new_vote;
1002 let form: CreateCommentLike = {
1003 comment_id: i.comment_view.comment.id,
1004 score: this.state.my_vote,
1008 WebSocketService.Instance.send(wsClient.likeComment(form));
1009 this.setState(this.state);
1013 handleModRemoveShow(i: CommentNode) {
1014 i.state.showRemoveDialog = true;
1015 i.setState(i.state);
1018 handleModRemoveReasonChange(i: CommentNode, event: any) {
1019 i.state.removeReason = event.target.value;
1020 i.setState(i.state);
1023 handleModRemoveDataChange(i: CommentNode, event: any) {
1024 i.state.removeData = event.target.checked;
1025 i.setState(i.state);
1028 handleModRemoveSubmit(i: CommentNode) {
1029 let comment = i.props.node.comment_view.comment;
1030 let form: RemoveComment = {
1031 comment_id: comment.id,
1032 removed: !comment.removed,
1033 reason: i.state.removeReason,
1036 WebSocketService.Instance.send(wsClient.removeComment(form));
1038 i.state.showRemoveDialog = false;
1039 i.setState(i.state);
1042 isPersonMentionType(
1043 item: CommentView | PersonMentionView
1044 ): item is PersonMentionView {
1045 return (item as PersonMentionView).person_mention?.id !== undefined;
1048 handleMarkRead(i: CommentNode) {
1049 if (i.isPersonMentionType(i.props.node.comment_view)) {
1050 let form: MarkPersonMentionAsRead = {
1051 person_mention_id: i.props.node.comment_view.person_mention.id,
1052 read: !i.props.node.comment_view.person_mention.read,
1055 WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
1057 let form: MarkCommentAsRead = {
1058 comment_id: i.props.node.comment_view.comment.id,
1059 read: !i.props.node.comment_view.comment.read,
1062 WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
1065 i.state.readLoading = true;
1066 i.setState(this.state);
1069 handleModBanFromCommunityShow(i: CommentNode) {
1070 i.state.showBanDialog = !i.state.showBanDialog;
1071 i.state.banType = BanType.Community;
1072 i.setState(i.state);
1075 handleModBanShow(i: CommentNode) {
1076 i.state.showBanDialog = !i.state.showBanDialog;
1077 i.state.banType = BanType.Site;
1078 i.setState(i.state);
1081 handleModBanReasonChange(i: CommentNode, event: any) {
1082 i.state.banReason = event.target.value;
1083 i.setState(i.state);
1086 handleModBanExpiresChange(i: CommentNode, event: any) {
1087 i.state.banExpires = event.target.value;
1088 i.setState(i.state);
1091 handleModBanFromCommunitySubmit(i: CommentNode) {
1092 i.state.banType = BanType.Community;
1093 i.setState(i.state);
1094 i.handleModBanBothSubmit(i);
1097 handleModBanSubmit(i: CommentNode) {
1098 i.state.banType = BanType.Site;
1099 i.setState(i.state);
1100 i.handleModBanBothSubmit(i);
1103 handleModBanBothSubmit(i: CommentNode) {
1104 let cv = i.props.node.comment_view;
1106 if (i.state.banType == BanType.Community) {
1107 // If its an unban, restore all their data
1108 let ban = !cv.creator_banned_from_community;
1110 i.state.removeData = false;
1112 let form: BanFromCommunity = {
1113 person_id: cv.creator.id,
1114 community_id: cv.community.id,
1116 remove_data: i.state.removeData,
1117 reason: i.state.banReason,
1118 expires: getUnixTime(i.state.banExpires),
1121 WebSocketService.Instance.send(wsClient.banFromCommunity(form));
1123 // If its an unban, restore all their data
1124 let ban = !cv.creator.banned;
1126 i.state.removeData = false;
1128 let form: BanPerson = {
1129 person_id: cv.creator.id,
1131 remove_data: i.state.removeData,
1132 reason: i.state.banReason,
1133 expires: getUnixTime(i.state.banExpires),
1136 WebSocketService.Instance.send(wsClient.banPerson(form));
1139 i.state.showBanDialog = false;
1140 i.setState(i.state);
1143 handleShowConfirmAppointAsMod(i: CommentNode) {
1144 i.state.showConfirmAppointAsMod = true;
1145 i.setState(i.state);
1148 handleCancelConfirmAppointAsMod(i: CommentNode) {
1149 i.state.showConfirmAppointAsMod = false;
1150 i.setState(i.state);
1153 handleAddModToCommunity(i: CommentNode) {
1154 let cv = i.props.node.comment_view;
1155 let form: AddModToCommunity = {
1156 person_id: cv.creator.id,
1157 community_id: cv.community.id,
1161 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
1162 i.state.showConfirmAppointAsMod = false;
1163 i.setState(i.state);
1166 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1167 i.state.showConfirmAppointAsAdmin = true;
1168 i.setState(i.state);
1171 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1172 i.state.showConfirmAppointAsAdmin = false;
1173 i.setState(i.state);
1176 handleAddAdmin(i: CommentNode) {
1177 let form: AddAdmin = {
1178 person_id: i.props.node.comment_view.creator.id,
1182 WebSocketService.Instance.send(wsClient.addAdmin(form));
1183 i.state.showConfirmAppointAsAdmin = false;
1184 i.setState(i.state);
1187 handleShowConfirmTransferCommunity(i: CommentNode) {
1188 i.state.showConfirmTransferCommunity = true;
1189 i.setState(i.state);
1192 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1193 i.state.showConfirmTransferCommunity = false;
1194 i.setState(i.state);
1197 handleTransferCommunity(i: CommentNode) {
1198 let cv = i.props.node.comment_view;
1199 let form: TransferCommunity = {
1200 community_id: cv.community.id,
1201 person_id: cv.creator.id,
1204 WebSocketService.Instance.send(wsClient.transferCommunity(form));
1205 i.state.showConfirmTransferCommunity = false;
1206 i.setState(i.state);
1209 handleShowConfirmTransferSite(i: CommentNode) {
1210 i.state.showConfirmTransferSite = true;
1211 i.setState(i.state);
1214 handleCancelShowConfirmTransferSite(i: CommentNode) {
1215 i.state.showConfirmTransferSite = false;
1216 i.setState(i.state);
1219 handleTransferSite(i: CommentNode) {
1220 let form: TransferSite = {
1221 person_id: i.props.node.comment_view.creator.id,
1224 WebSocketService.Instance.send(wsClient.transferSite(form));
1225 i.state.showConfirmTransferSite = false;
1226 i.setState(i.state);
1229 get isCommentNew(): boolean {
1230 let now = moment.utc().subtract(10, "minutes");
1231 let then = moment.utc(this.props.node.comment_view.comment.published);
1232 return now.isBefore(then);
1235 handleCommentCollapse(i: CommentNode) {
1236 i.state.collapsed = !i.state.collapsed;
1237 i.setState(i.state);
1241 handleViewSource(i: CommentNode) {
1242 i.state.viewSource = !i.state.viewSource;
1243 i.setState(i.state);
1246 handleShowAdvanced(i: CommentNode) {
1247 i.state.showAdvanced = !i.state.showAdvanced;
1248 i.setState(i.state);
1253 if (this.state.my_vote == 1) {
1255 } else if (this.state.my_vote == -1) {
1256 return "text-danger";
1258 return "text-muted";
1262 get pointsTippy(): string {
1263 let points = i18n.t("number_of_points", {
1264 count: this.state.score,
1267 let upvotes = i18n.t("number_of_upvotes", {
1268 count: this.state.upvotes,
1271 let downvotes = i18n.t("number_of_downvotes", {
1272 count: this.state.downvotes,
1275 return `${points} • ${upvotes} • ${downvotes}`;
1278 get expandText(): string {
1279 return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse");