1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
8 MarkPersonMentionAsRead,
12 CommunityModeratorView,
20 } from "lemmy-js-client";
21 import { CommentNode as CommentNodeI, BanType } from "../interfaces";
22 import { WebSocketService, UserService } from "../services";
34 import moment from "moment";
35 import { MomentTime } from "./moment-time";
36 import { CommentForm } from "./comment-form";
37 import { CommentNodes } from "./comment-nodes";
38 import { PersonListing } from "./person-listing";
39 import { CommunityLink } from "./community-link";
40 import { Icon, Spinner } from "./icon";
41 import { i18n } from "../i18next";
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)}
197 this.state.collapsed ? i18n.t("expand") : i18n.t("collapse")
200 {this.state.collapsed ? "+" : "—"}
202 {/* This is an expanding spacer for mobile */}
203 <div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
207 className={`unselectable pointer ${this.scoreColor}`}
208 onClick={linkEvent(node, this.handleCommentUpvote)}
209 data-tippy-content={this.pointsTippy}
212 class="mr-1 font-weight-bold"
213 aria-label={i18n.t("number_of_points", {
214 count: this.state.score,
220 <span className="mr-1">•</span>
224 <MomentTime data={cv.comment} />
227 {/* end of user row */}
228 {this.state.showEdit && (
232 onReplyCancel={this.handleReplyCancel}
233 disabled={this.props.locked}
237 {!this.state.showEdit && !this.state.collapsed && (
239 {this.state.viewSource ? (
240 <pre>{this.commentUnlessRemoved}</pre>
244 dangerouslySetInnerHTML={mdToHtml(
245 this.commentUnlessRemoved
249 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
250 {this.props.showContext && this.linkBtn}
251 {this.props.markable && (
253 class="btn btn-link btn-animate text-muted"
254 onClick={linkEvent(this, this.handleMarkRead)}
256 this.commentOrMentionRead
257 ? i18n.t("mark_as_unread")
258 : i18n.t("mark_as_read")
261 this.commentOrMentionRead
262 ? i18n.t("mark_as_unread")
263 : i18n.t("mark_as_read")
266 {this.state.readLoading ? (
271 classes={`icon-inline ${
272 this.commentOrMentionRead && "text-success"
278 {UserService.Instance.localUserView && !this.props.viewOnly && (
281 className={`btn btn-link btn-animate ${
282 this.state.my_vote == 1 ? "text-info" : "text-muted"
284 onClick={linkEvent(node, this.handleCommentUpvote)}
285 data-tippy-content={i18n.t("upvote")}
286 aria-label={i18n.t("upvote")}
288 <Icon icon="arrow-up1" classes="icon-inline" />
290 this.state.upvotes !== this.state.score && (
291 <span class="ml-1">{this.state.upvotes}</span>
294 {this.props.enableDownvotes && (
296 className={`btn btn-link btn-animate ${
297 this.state.my_vote == -1
301 onClick={linkEvent(node, this.handleCommentDownvote)}
302 data-tippy-content={i18n.t("downvote")}
303 aria-label={i18n.t("downvote")}
305 <Icon icon="arrow-down1" classes="icon-inline" />
307 this.state.upvotes !== this.state.score && (
308 <span class="ml-1">{this.state.downvotes}</span>
313 class="btn btn-link btn-animate text-muted"
314 onClick={linkEvent(this, this.handleReplyClick)}
315 data-tippy-content={i18n.t("reply")}
316 aria-label={i18n.t("reply")}
318 <Icon icon="reply1" classes="icon-inline" />
320 {!this.state.showAdvanced ? (
322 className="btn btn-link btn-animate text-muted"
323 onClick={linkEvent(this, this.handleShowAdvanced)}
324 data-tippy-content={i18n.t("more")}
325 aria-label={i18n.t("more")}
327 <Icon icon="more-vertical" classes="icon-inline" />
331 {!this.myComment && (
332 <button class="btn btn-link btn-animate">
334 className="text-muted"
335 to={`/create_private_message/recipient/${cv.creator.id}`}
336 title={i18n.t("message").toLowerCase()}
342 {!this.props.showContext && this.linkBtn}
344 class="btn btn-link btn-animate text-muted"
347 this.handleSaveCommentClick
350 cv.saved ? i18n.t("unsave") : i18n.t("save")
353 cv.saved ? i18n.t("unsave") : i18n.t("save")
356 {this.state.saveLoading ? (
361 classes={`icon-inline ${
362 cv.saved && "text-warning"
368 className="btn btn-link btn-animate text-muted"
369 onClick={linkEvent(this, this.handleViewSource)}
370 data-tippy-content={i18n.t("view_source")}
371 aria-label={i18n.t("view_source")}
375 classes={`icon-inline ${
376 this.state.viewSource && "text-success"
383 class="btn btn-link btn-animate text-muted"
384 onClick={linkEvent(this, this.handleEditClick)}
385 data-tippy-content={i18n.t("edit")}
386 aria-label={i18n.t("edit")}
388 <Icon icon="edit" classes="icon-inline" />
391 class="btn btn-link btn-animate text-muted"
394 this.handleDeleteClick
409 classes={`icon-inline ${
410 cv.comment.deleted && "text-danger"
416 {/* Admins and mods can remove comments */}
417 {(this.canMod || this.canAdmin) && (
419 {!cv.comment.removed ? (
421 class="btn btn-link btn-animate text-muted"
424 this.handleModRemoveShow
426 aria-label={i18n.t("remove")}
432 class="btn btn-link btn-animate text-muted"
435 this.handleModRemoveSubmit
437 aria-label={i18n.t("restore")}
444 {/* Mods can ban from community, and appoint as mods to community */}
448 (!cv.creator_banned_from_community ? (
450 class="btn btn-link btn-animate text-muted"
453 this.handleModBanFromCommunityShow
455 aria-label={i18n.t("ban")}
461 class="btn btn-link btn-animate text-muted"
464 this.handleModBanFromCommunitySubmit
466 aria-label={i18n.t("unban")}
471 {!cv.creator_banned_from_community &&
472 (!this.state.showConfirmAppointAsMod ? (
474 class="btn btn-link btn-animate text-muted"
477 this.handleShowConfirmAppointAsMod
481 ? i18n.t("remove_as_mod")
482 : i18n.t("appoint_as_mod")
486 ? i18n.t("remove_as_mod")
487 : i18n.t("appoint_as_mod")}
492 class="btn btn-link btn-animate text-muted"
493 aria-label={i18n.t("are_you_sure")}
495 {i18n.t("are_you_sure")}
498 class="btn btn-link btn-animate text-muted"
501 this.handleAddModToCommunity
503 aria-label={i18n.t("yes")}
508 class="btn btn-link btn-animate text-muted"
511 this.handleCancelConfirmAppointAsMod
513 aria-label={i18n.t("no")}
521 {/* Community creators and admins can transfer community to another mod */}
522 {(this.amCommunityCreator || this.canAdmin) &&
525 (!this.state.showConfirmTransferCommunity ? (
527 class="btn btn-link btn-animate text-muted"
530 this.handleShowConfirmTransferCommunity
532 aria-label={i18n.t("transfer_community")}
534 {i18n.t("transfer_community")}
539 class="btn btn-link btn-animate text-muted"
540 aria-label={i18n.t("are_you_sure")}
542 {i18n.t("are_you_sure")}
545 class="btn btn-link btn-animate text-muted"
548 this.handleTransferCommunity
550 aria-label={i18n.t("yes")}
555 class="btn btn-link btn-animate text-muted"
559 .handleCancelShowConfirmTransferCommunity
561 aria-label={i18n.t("no")}
567 {/* Admins can ban from all, and appoint other admins */}
571 (!cv.creator.banned ? (
573 class="btn btn-link btn-animate text-muted"
576 this.handleModBanShow
578 aria-label={i18n.t("ban_from_site")}
580 {i18n.t("ban_from_site")}
584 class="btn btn-link btn-animate text-muted"
587 this.handleModBanSubmit
589 aria-label={i18n.t("unban_from_site")}
591 {i18n.t("unban_from_site")}
594 {!cv.creator.banned &&
596 (!this.state.showConfirmAppointAsAdmin ? (
598 class="btn btn-link btn-animate text-muted"
601 this.handleShowConfirmAppointAsAdmin
605 ? i18n.t("remove_as_admin")
606 : i18n.t("appoint_as_admin")
610 ? i18n.t("remove_as_admin")
611 : i18n.t("appoint_as_admin")}
615 <button class="btn btn-link btn-animate text-muted">
616 {i18n.t("are_you_sure")}
619 class="btn btn-link btn-animate text-muted"
624 aria-label={i18n.t("yes")}
629 class="btn btn-link btn-animate text-muted"
632 this.handleCancelConfirmAppointAsAdmin
634 aria-label={i18n.t("no")}
642 {/* Site Creator can transfer to another admin */}
643 {this.amSiteCreator &&
646 (!this.state.showConfirmTransferSite ? (
648 class="btn btn-link btn-animate text-muted"
651 this.handleShowConfirmTransferSite
653 aria-label={i18n.t("transfer_site")}
655 {i18n.t("transfer_site")}
660 class="btn btn-link btn-animate text-muted"
661 aria-label={i18n.t("are_you_sure")}
663 {i18n.t("are_you_sure")}
666 class="btn btn-link btn-animate text-muted"
669 this.handleTransferSite
671 aria-label={i18n.t("yes")}
676 class="btn btn-link btn-animate text-muted"
679 this.handleCancelShowConfirmTransferSite
681 aria-label={i18n.t("no")}
692 {/* end of button group */}
697 {/* end of details */}
698 {this.state.showRemoveDialog && (
701 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
705 htmlFor={`mod-remove-reason-${cv.comment.id}`}
711 id={`mod-remove-reason-${cv.comment.id}`}
712 class="form-control mr-2"
713 placeholder={i18n.t("reason")}
714 value={this.state.removeReason}
715 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
719 class="btn btn-secondary"
720 aria-label={i18n.t("remove_comment")}
722 {i18n.t("remove_comment")}
726 {this.state.showBanDialog && (
727 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
728 <div class="form-group row">
730 class="col-form-label"
731 htmlFor={`mod-ban-reason-${cv.comment.id}`}
737 id={`mod-ban-reason-${cv.comment.id}`}
738 class="form-control mr-2"
739 placeholder={i18n.t("reason")}
740 value={this.state.banReason}
741 onInput={linkEvent(this, this.handleModBanReasonChange)}
743 <div class="form-group">
744 <div class="form-check">
746 class="form-check-input"
747 id="mod-ban-remove-data"
749 checked={this.state.removeData}
750 onChange={linkEvent(this, this.handleModRemoveDataChange)}
752 <label class="form-check-label" htmlFor="mod-ban-remove-data">
753 {i18n.t("remove_posts_comments")}
758 {/* TODO hold off on expires until later */}
759 {/* <div class="form-group row"> */}
760 {/* <label class="col-form-label">Expires</label> */}
761 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
763 <div class="form-group row">
766 class="btn btn-secondary"
767 aria-label={i18n.t("ban")}
769 {i18n.t("ban")} {cv.creator.name}
774 {this.state.showReply && (
777 onReplyCancel={this.handleReplyCancel}
778 disabled={this.props.locked}
782 {node.children && !this.state.collapsed && (
784 nodes={node.children}
785 locked={this.props.locked}
786 moderators={this.props.moderators}
787 admins={this.props.admins}
788 postCreatorId={this.props.postCreatorId}
789 enableDownvotes={this.props.enableDownvotes}
792 {/* A collapsed clearfix */}
793 {this.state.collapsed && <div class="row col-12"></div>}
798 get commentOrMentionRead() {
799 let cv = this.props.node.comment_view;
800 return this.isPersonMentionType(cv)
801 ? cv.person_mention.read
806 let cv = this.props.node.comment_view;
809 className="btn btn-link btn-animate text-muted"
810 to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
811 title={this.props.showContext ? i18n.t("show_context") : i18n.t("link")}
813 <Icon icon="link" classes="icon-inline" />
822 get myComment(): boolean {
824 this.props.node.comment_view.creator.id ==
825 UserService.Instance.localUserView?.person.id
829 get isMod(): boolean {
831 this.props.moderators &&
833 this.props.moderators.map(m => m.moderator.id),
834 this.props.node.comment_view.creator.id
839 get isAdmin(): boolean {
843 this.props.admins.map(a => a.person.id),
844 this.props.node.comment_view.creator.id
849 get isPostCreator(): boolean {
850 return this.props.node.comment_view.creator.id == this.props.postCreatorId;
853 get canMod(): boolean {
854 if (this.props.admins && this.props.moderators) {
855 let adminsThenMods = this.props.admins
856 .map(a => a.person.id)
857 .concat(this.props.moderators.map(m => m.moderator.id));
860 UserService.Instance.localUserView,
862 this.props.node.comment_view.creator.id
869 get canAdmin(): boolean {
873 UserService.Instance.localUserView,
874 this.props.admins.map(a => a.person.id),
875 this.props.node.comment_view.creator.id
880 get amCommunityCreator(): boolean {
882 this.props.moderators &&
883 UserService.Instance.localUserView &&
884 this.props.node.comment_view.creator.id !=
885 UserService.Instance.localUserView.person.id &&
886 UserService.Instance.localUserView.person.id ==
887 this.props.moderators[0].moderator.id
891 get amSiteCreator(): boolean {
894 UserService.Instance.localUserView &&
895 this.props.node.comment_view.creator.id !=
896 UserService.Instance.localUserView.person.id &&
897 UserService.Instance.localUserView.person.id ==
898 this.props.admins[0].person.id
902 get commentUnlessRemoved(): string {
903 let comment = this.props.node.comment_view.comment;
904 return comment.removed
905 ? `*${i18n.t("removed")}*`
907 ? `*${i18n.t("deleted")}*`
911 handleReplyClick(i: CommentNode) {
912 i.state.showReply = true;
916 handleEditClick(i: CommentNode) {
917 i.state.showEdit = true;
921 handleDeleteClick(i: CommentNode) {
922 let comment = i.props.node.comment_view.comment;
923 let deleteForm: DeleteComment = {
924 comment_id: comment.id,
925 deleted: !comment.deleted,
928 WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
931 handleSaveCommentClick(i: CommentNode) {
932 let cv = i.props.node.comment_view;
933 let save = cv.saved == undefined ? true : !cv.saved;
934 let form: SaveComment = {
935 comment_id: cv.comment.id,
940 WebSocketService.Instance.send(wsClient.saveComment(form));
942 i.state.saveLoading = true;
943 i.setState(this.state);
946 handleReplyCancel() {
947 this.state.showReply = false;
948 this.state.showEdit = false;
949 this.setState(this.state);
952 handleCommentUpvote(i: CommentNodeI, event: any) {
953 event.preventDefault();
954 let new_vote = this.state.my_vote == 1 ? 0 : 1;
956 if (this.state.my_vote == 1) {
958 this.state.upvotes--;
959 } else if (this.state.my_vote == -1) {
960 this.state.downvotes--;
961 this.state.upvotes++;
962 this.state.score += 2;
964 this.state.upvotes++;
968 this.state.my_vote = new_vote;
970 let form: CreateCommentLike = {
971 comment_id: i.comment_view.comment.id,
972 score: this.state.my_vote,
976 WebSocketService.Instance.send(wsClient.likeComment(form));
977 this.setState(this.state);
981 handleCommentDownvote(i: CommentNodeI, event: any) {
982 event.preventDefault();
983 let new_vote = this.state.my_vote == -1 ? 0 : -1;
985 if (this.state.my_vote == 1) {
986 this.state.score -= 2;
987 this.state.upvotes--;
988 this.state.downvotes++;
989 } else if (this.state.my_vote == -1) {
990 this.state.downvotes--;
993 this.state.downvotes++;
997 this.state.my_vote = new_vote;
999 let form: CreateCommentLike = {
1000 comment_id: i.comment_view.comment.id,
1001 score: this.state.my_vote,
1005 WebSocketService.Instance.send(wsClient.likeComment(form));
1006 this.setState(this.state);
1010 handleModRemoveShow(i: CommentNode) {
1011 i.state.showRemoveDialog = true;
1012 i.setState(i.state);
1015 handleModRemoveReasonChange(i: CommentNode, event: any) {
1016 i.state.removeReason = event.target.value;
1017 i.setState(i.state);
1020 handleModRemoveDataChange(i: CommentNode, event: any) {
1021 i.state.removeData = event.target.checked;
1022 i.setState(i.state);
1025 handleModRemoveSubmit(i: CommentNode) {
1026 let comment = i.props.node.comment_view.comment;
1027 let form: RemoveComment = {
1028 comment_id: comment.id,
1029 removed: !comment.removed,
1030 reason: i.state.removeReason,
1033 WebSocketService.Instance.send(wsClient.removeComment(form));
1035 i.state.showRemoveDialog = false;
1036 i.setState(i.state);
1039 isPersonMentionType(
1040 item: CommentView | PersonMentionView
1041 ): item is PersonMentionView {
1042 return (item as PersonMentionView).person_mention?.id !== undefined;
1045 handleMarkRead(i: CommentNode) {
1046 if (i.isPersonMentionType(i.props.node.comment_view)) {
1047 let form: MarkPersonMentionAsRead = {
1048 person_mention_id: i.props.node.comment_view.person_mention.id,
1049 read: !i.props.node.comment_view.person_mention.read,
1052 WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
1054 let form: MarkCommentAsRead = {
1055 comment_id: i.props.node.comment_view.comment.id,
1056 read: !i.props.node.comment_view.comment.read,
1059 WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
1062 i.state.readLoading = true;
1063 i.setState(this.state);
1066 handleModBanFromCommunityShow(i: CommentNode) {
1067 i.state.showBanDialog = !i.state.showBanDialog;
1068 i.state.banType = BanType.Community;
1069 i.setState(i.state);
1072 handleModBanShow(i: CommentNode) {
1073 i.state.showBanDialog = !i.state.showBanDialog;
1074 i.state.banType = BanType.Site;
1075 i.setState(i.state);
1078 handleModBanReasonChange(i: CommentNode, event: any) {
1079 i.state.banReason = event.target.value;
1080 i.setState(i.state);
1083 handleModBanExpiresChange(i: CommentNode, event: any) {
1084 i.state.banExpires = event.target.value;
1085 i.setState(i.state);
1088 handleModBanFromCommunitySubmit(i: CommentNode) {
1089 i.state.banType = BanType.Community;
1090 i.setState(i.state);
1091 i.handleModBanBothSubmit(i);
1094 handleModBanSubmit(i: CommentNode) {
1095 i.state.banType = BanType.Site;
1096 i.setState(i.state);
1097 i.handleModBanBothSubmit(i);
1100 handleModBanBothSubmit(i: CommentNode) {
1101 let cv = i.props.node.comment_view;
1103 if (i.state.banType == BanType.Community) {
1104 // If its an unban, restore all their data
1105 let ban = !cv.creator_banned_from_community;
1107 i.state.removeData = false;
1109 let form: BanFromCommunity = {
1110 person_id: cv.creator.id,
1111 community_id: cv.community.id,
1113 remove_data: i.state.removeData,
1114 reason: i.state.banReason,
1115 expires: getUnixTime(i.state.banExpires),
1118 WebSocketService.Instance.send(wsClient.banFromCommunity(form));
1120 // If its an unban, restore all their data
1121 let ban = !cv.creator.banned;
1123 i.state.removeData = false;
1125 let form: BanPerson = {
1126 person_id: cv.creator.id,
1128 remove_data: i.state.removeData,
1129 reason: i.state.banReason,
1130 expires: getUnixTime(i.state.banExpires),
1133 WebSocketService.Instance.send(wsClient.banPerson(form));
1136 i.state.showBanDialog = false;
1137 i.setState(i.state);
1140 handleShowConfirmAppointAsMod(i: CommentNode) {
1141 i.state.showConfirmAppointAsMod = true;
1142 i.setState(i.state);
1145 handleCancelConfirmAppointAsMod(i: CommentNode) {
1146 i.state.showConfirmAppointAsMod = false;
1147 i.setState(i.state);
1150 handleAddModToCommunity(i: CommentNode) {
1151 let cv = i.props.node.comment_view;
1152 let form: AddModToCommunity = {
1153 person_id: cv.creator.id,
1154 community_id: cv.community.id,
1158 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
1159 i.state.showConfirmAppointAsMod = false;
1160 i.setState(i.state);
1163 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1164 i.state.showConfirmAppointAsAdmin = true;
1165 i.setState(i.state);
1168 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1169 i.state.showConfirmAppointAsAdmin = false;
1170 i.setState(i.state);
1173 handleAddAdmin(i: CommentNode) {
1174 let form: AddAdmin = {
1175 person_id: i.props.node.comment_view.creator.id,
1179 WebSocketService.Instance.send(wsClient.addAdmin(form));
1180 i.state.showConfirmAppointAsAdmin = false;
1181 i.setState(i.state);
1184 handleShowConfirmTransferCommunity(i: CommentNode) {
1185 i.state.showConfirmTransferCommunity = true;
1186 i.setState(i.state);
1189 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1190 i.state.showConfirmTransferCommunity = false;
1191 i.setState(i.state);
1194 handleTransferCommunity(i: CommentNode) {
1195 let cv = i.props.node.comment_view;
1196 let form: TransferCommunity = {
1197 community_id: cv.community.id,
1198 person_id: cv.creator.id,
1201 WebSocketService.Instance.send(wsClient.transferCommunity(form));
1202 i.state.showConfirmTransferCommunity = false;
1203 i.setState(i.state);
1206 handleShowConfirmTransferSite(i: CommentNode) {
1207 i.state.showConfirmTransferSite = true;
1208 i.setState(i.state);
1211 handleCancelShowConfirmTransferSite(i: CommentNode) {
1212 i.state.showConfirmTransferSite = false;
1213 i.setState(i.state);
1216 handleTransferSite(i: CommentNode) {
1217 let form: TransferSite = {
1218 person_id: i.props.node.comment_view.creator.id,
1221 WebSocketService.Instance.send(wsClient.transferSite(form));
1222 i.state.showConfirmTransferSite = false;
1223 i.setState(i.state);
1226 get isCommentNew(): boolean {
1227 let now = moment.utc().subtract(10, "minutes");
1228 let then = moment.utc(this.props.node.comment_view.comment.published);
1229 return now.isBefore(then);
1232 handleCommentCollapse(i: CommentNode) {
1233 i.state.collapsed = !i.state.collapsed;
1234 i.setState(i.state);
1237 handleViewSource(i: CommentNode) {
1238 i.state.viewSource = !i.state.viewSource;
1239 i.setState(i.state);
1242 handleShowAdvanced(i: CommentNode) {
1243 i.state.showAdvanced = !i.state.showAdvanced;
1244 i.setState(i.state);
1249 if (this.state.my_vote == 1) {
1251 } else if (this.state.my_vote == -1) {
1252 return "text-danger";
1254 return "text-muted";
1258 get pointsTippy(): string {
1259 let points = i18n.t("number_of_points", {
1260 count: this.state.score,
1263 let upvotes = i18n.t("number_of_upvotes", {
1264 count: this.state.upvotes,
1267 let downvotes = i18n.t("number_of_downvotes", {
1268 count: this.state.downvotes,
1271 return `${points} • ${upvotes} • ${downvotes}`;