1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
10 CommunityModeratorView,
15 MarkPersonMentionAsRead,
22 } from "lemmy-js-client";
23 import moment from "moment";
24 import { i18n } from "../../i18next";
25 import { BanType, CommentNode as CommentNodeI } from "../../interfaces";
26 import { UserService, WebSocketService } from "../../services";
40 import { Icon, Spinner } from "../common/icon";
41 import { MomentTime } from "../common/moment-time";
42 import { CommunityLink } from "../community/community-link";
43 import { PersonListing } from "../person/person-listing";
44 import { CommentForm } from "./comment-form";
45 import { CommentNodes } from "./comment-nodes";
47 interface CommentNodeState {
50 showRemoveDialog: boolean;
52 showBanDialog: boolean;
55 banExpireDays: number;
57 showConfirmTransferSite: boolean;
58 showConfirmTransferCommunity: boolean;
59 showConfirmAppointAsMod: boolean;
60 showConfirmAppointAsAdmin: boolean;
63 showAdvanced: boolean;
64 showReportDialog: boolean;
75 interface CommentNodeProps {
82 showContext?: boolean;
83 moderators: CommunityModeratorView[];
84 admins: PersonViewSafe[];
85 // TODO is this necessary, can't I get it from the node itself?
86 postCreatorId?: number;
87 showCommunity?: boolean;
88 enableDownvotes: boolean;
91 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
92 private emptyState: CommentNodeState = {
95 showRemoveDialog: false,
101 banType: BanType.Community,
105 showConfirmTransferSite: false,
106 showConfirmTransferCommunity: false,
107 showConfirmAppointAsMod: false,
108 showConfirmAppointAsAdmin: false,
109 showReportDialog: false,
111 my_vote: this.props.node.comment_view.my_vote,
112 score: this.props.node.comment_view.counts.score,
113 upvotes: this.props.node.comment_view.counts.upvotes,
114 downvotes: this.props.node.comment_view.counts.downvotes,
115 borderColor: this.props.node.depth
116 ? colorList[this.props.node.depth % colorList.length]
122 constructor(props: any, context: any) {
123 super(props, context);
125 this.state = this.emptyState;
126 this.handleReplyCancel = this.handleReplyCancel.bind(this);
127 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
128 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
131 // TODO see if there's a better way to do this, and all willReceiveProps
132 componentWillReceiveProps(nextProps: CommentNodeProps) {
133 let cv = nextProps.node.comment_view;
134 this.state.my_vote = cv.my_vote;
135 this.state.upvotes = cv.counts.upvotes;
136 this.state.downvotes = cv.counts.downvotes;
137 this.state.score = cv.counts.score;
138 this.state.readLoading = false;
139 this.state.saveLoading = false;
140 this.setState(this.state);
144 let node = this.props.node;
145 let cv = this.props.node.comment_view;
148 className={`comment ${
149 cv.comment.parent_id && !this.props.noIndent ? "ml-1" : ""
153 id={`comment-${cv.comment.id}`}
154 className={`details comment-node py-2 ${
155 !this.props.noBorder ? "border-top border-light" : ""
156 } ${this.isCommentNew ? "mark" : ""}`}
158 !this.props.noIndent &&
159 cv.comment.parent_id &&
160 `border-left: 2px ${this.state.borderColor} solid !important`
164 class={`${!this.props.noIndent && cv.comment.parent_id && "ml-2"}`}
166 <div class="d-flex flex-wrap align-items-center text-muted small">
168 <PersonListing person={cv.creator} />
172 <div className="badge badge-light d-none d-sm-inline mr-2">
177 <div className="badge badge-light d-none d-sm-inline mr-2">
181 {this.isPostCreator && (
182 <div className="badge badge-light d-none d-sm-inline mr-2">
186 {cv.creator.bot_account && (
187 <div className="badge badge-light d-none d-sm-inline mr-2">
188 {i18n.t("bot_account").toLowerCase()}
191 {(cv.creator_banned_from_community || isBanned(cv.creator)) && (
192 <div className="badge badge-danger mr-2">
196 {this.props.showCommunity && (
198 <span class="mx-1">{i18n.t("to")}</span>
199 <CommunityLink community={cv.community} />
200 <span class="mx-2">•</span>
201 <Link className="mr-2" to={`/post/${cv.post.id}`}>
207 class="btn btn-sm text-muted"
208 onClick={linkEvent(this, this.handleCommentCollapse)}
209 aria-label={this.expandText}
210 data-tippy-content={this.expandText}
212 {this.state.collapsed ? (
213 <Icon icon="plus-square" classes="icon-inline" />
215 <Icon icon="minus-square" classes="icon-inline" />
219 {/* This is an expanding spacer for mobile */}
220 <div className="mr-lg-5 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
224 className={`unselectable pointer ${this.scoreColor}`}
225 onClick={linkEvent(node, this.handleCommentUpvote)}
226 data-tippy-content={this.pointsTippy}
229 class="mr-1 font-weight-bold"
230 aria-label={i18n.t("number_of_points", {
231 count: this.state.score,
232 formattedCount: this.state.score,
235 {numToSI(this.state.score)}
238 <span className="mr-1">•</span>
242 <MomentTime data={cv.comment} />
245 {/* end of user row */}
246 {this.state.showEdit && (
250 onReplyCancel={this.handleReplyCancel}
251 disabled={this.props.locked}
255 {!this.state.showEdit && !this.state.collapsed && (
257 {this.state.viewSource ? (
258 <pre>{this.commentUnlessRemoved}</pre>
262 dangerouslySetInnerHTML={mdToHtml(
263 this.commentUnlessRemoved
267 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
268 {this.props.showContext && this.linkBtn()}
269 {this.props.markable && (
271 class="btn btn-link btn-animate text-muted"
272 onClick={linkEvent(this, this.handleMarkRead)}
274 this.commentOrMentionRead
275 ? i18n.t("mark_as_unread")
276 : i18n.t("mark_as_read")
279 this.commentOrMentionRead
280 ? i18n.t("mark_as_unread")
281 : i18n.t("mark_as_read")
284 {this.state.readLoading ? (
289 classes={`icon-inline ${
290 this.commentOrMentionRead && "text-success"
296 {UserService.Instance.myUserInfo && !this.props.viewOnly && (
299 className={`btn btn-link btn-animate ${
300 this.state.my_vote == 1 ? "text-info" : "text-muted"
302 onClick={linkEvent(node, this.handleCommentUpvote)}
303 data-tippy-content={i18n.t("upvote")}
304 aria-label={i18n.t("upvote")}
306 <Icon icon="arrow-up1" classes="icon-inline" />
308 this.state.upvotes !== this.state.score && (
310 {numToSI(this.state.upvotes)}
314 {this.props.enableDownvotes && (
316 className={`btn btn-link btn-animate ${
317 this.state.my_vote == -1
321 onClick={linkEvent(node, this.handleCommentDownvote)}
322 data-tippy-content={i18n.t("downvote")}
323 aria-label={i18n.t("downvote")}
325 <Icon icon="arrow-down1" classes="icon-inline" />
327 this.state.upvotes !== this.state.score && (
329 {numToSI(this.state.downvotes)}
335 class="btn btn-link btn-animate text-muted"
336 onClick={linkEvent(this, this.handleReplyClick)}
337 data-tippy-content={i18n.t("reply")}
338 aria-label={i18n.t("reply")}
340 <Icon icon="reply1" classes="icon-inline" />
342 {!this.state.showAdvanced ? (
344 className="btn btn-link btn-animate text-muted"
345 onClick={linkEvent(this, this.handleShowAdvanced)}
346 data-tippy-content={i18n.t("more")}
347 aria-label={i18n.t("more")}
349 <Icon icon="more-vertical" classes="icon-inline" />
353 {!this.myComment && (
355 <button class="btn btn-link btn-animate">
357 className="text-muted"
358 to={`/create_private_message/recipient/${cv.creator.id}`}
359 title={i18n.t("message").toLowerCase()}
365 class="btn btn-link btn-animate text-muted"
368 this.handleShowReportDialog
370 data-tippy-content={i18n.t(
373 aria-label={i18n.t("show_report_dialog")}
378 class="btn btn-link btn-animate text-muted"
381 this.handleBlockUserClick
383 data-tippy-content={i18n.t("block_user")}
384 aria-label={i18n.t("block_user")}
386 <Icon icon="slash" />
391 class="btn btn-link btn-animate text-muted"
394 this.handleSaveCommentClick
397 cv.saved ? i18n.t("unsave") : i18n.t("save")
400 cv.saved ? i18n.t("unsave") : i18n.t("save")
403 {this.state.saveLoading ? (
408 classes={`icon-inline ${
409 cv.saved && "text-warning"
415 className="btn btn-link btn-animate text-muted"
416 onClick={linkEvent(this, this.handleViewSource)}
417 data-tippy-content={i18n.t("view_source")}
418 aria-label={i18n.t("view_source")}
422 classes={`icon-inline ${
423 this.state.viewSource && "text-success"
430 class="btn btn-link btn-animate text-muted"
431 onClick={linkEvent(this, this.handleEditClick)}
432 data-tippy-content={i18n.t("edit")}
433 aria-label={i18n.t("edit")}
435 <Icon icon="edit" classes="icon-inline" />
438 class="btn btn-link btn-animate text-muted"
441 this.handleDeleteClick
456 classes={`icon-inline ${
457 cv.comment.deleted && "text-danger"
463 {/* Admins and mods can remove comments */}
464 {(this.canMod || this.canAdmin) && (
466 {!cv.comment.removed ? (
468 class="btn btn-link btn-animate text-muted"
471 this.handleModRemoveShow
473 aria-label={i18n.t("remove")}
479 class="btn btn-link btn-animate text-muted"
482 this.handleModRemoveSubmit
484 aria-label={i18n.t("restore")}
491 {/* Mods can ban from community, and appoint as mods to community */}
495 (!cv.creator_banned_from_community ? (
497 class="btn btn-link btn-animate text-muted"
500 this.handleModBanFromCommunityShow
502 aria-label={i18n.t("ban")}
508 class="btn btn-link btn-animate text-muted"
511 this.handleModBanFromCommunitySubmit
513 aria-label={i18n.t("unban")}
518 {!cv.creator_banned_from_community &&
519 (!this.state.showConfirmAppointAsMod ? (
521 class="btn btn-link btn-animate text-muted"
524 this.handleShowConfirmAppointAsMod
528 ? i18n.t("remove_as_mod")
529 : i18n.t("appoint_as_mod")
533 ? i18n.t("remove_as_mod")
534 : i18n.t("appoint_as_mod")}
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.handleAddModToCommunity
550 aria-label={i18n.t("yes")}
555 class="btn btn-link btn-animate text-muted"
558 this.handleCancelConfirmAppointAsMod
560 aria-label={i18n.t("no")}
568 {/* Community creators and admins can transfer community to another mod */}
569 {(this.amCommunityCreator || this.canAdmin) &&
572 (!this.state.showConfirmTransferCommunity ? (
574 class="btn btn-link btn-animate text-muted"
577 this.handleShowConfirmTransferCommunity
579 aria-label={i18n.t("transfer_community")}
581 {i18n.t("transfer_community")}
586 class="btn btn-link btn-animate text-muted"
587 aria-label={i18n.t("are_you_sure")}
589 {i18n.t("are_you_sure")}
592 class="btn btn-link btn-animate text-muted"
595 this.handleTransferCommunity
597 aria-label={i18n.t("yes")}
602 class="btn btn-link btn-animate text-muted"
606 .handleCancelShowConfirmTransferCommunity
608 aria-label={i18n.t("no")}
614 {/* Admins can ban from all, and appoint other admins */}
618 (!isBanned(cv.creator) ? (
620 class="btn btn-link btn-animate text-muted"
623 this.handleModBanShow
625 aria-label={i18n.t("ban_from_site")}
627 {i18n.t("ban_from_site")}
631 class="btn btn-link btn-animate text-muted"
634 this.handleModBanSubmit
636 aria-label={i18n.t("unban_from_site")}
638 {i18n.t("unban_from_site")}
641 {!isBanned(cv.creator) &&
643 (!this.state.showConfirmAppointAsAdmin ? (
645 class="btn btn-link btn-animate text-muted"
648 this.handleShowConfirmAppointAsAdmin
652 ? i18n.t("remove_as_admin")
653 : i18n.t("appoint_as_admin")
657 ? i18n.t("remove_as_admin")
658 : i18n.t("appoint_as_admin")}
662 <button class="btn btn-link btn-animate text-muted">
663 {i18n.t("are_you_sure")}
666 class="btn btn-link btn-animate text-muted"
671 aria-label={i18n.t("yes")}
676 class="btn btn-link btn-animate text-muted"
679 this.handleCancelConfirmAppointAsAdmin
681 aria-label={i18n.t("no")}
689 {/* Site Creator can transfer to another admin */}
690 {this.amSiteCreator &&
693 (!this.state.showConfirmTransferSite ? (
695 class="btn btn-link btn-animate text-muted"
698 this.handleShowConfirmTransferSite
700 aria-label={i18n.t("transfer_site")}
702 {i18n.t("transfer_site")}
707 class="btn btn-link btn-animate text-muted"
708 aria-label={i18n.t("are_you_sure")}
710 {i18n.t("are_you_sure")}
713 class="btn btn-link btn-animate text-muted"
716 this.handleTransferSite
718 aria-label={i18n.t("yes")}
723 class="btn btn-link btn-animate text-muted"
726 this.handleCancelShowConfirmTransferSite
728 aria-label={i18n.t("no")}
739 {/* end of button group */}
744 {/* end of details */}
745 {this.state.showRemoveDialog && (
748 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
752 htmlFor={`mod-remove-reason-${cv.comment.id}`}
758 id={`mod-remove-reason-${cv.comment.id}`}
759 class="form-control mr-2"
760 placeholder={i18n.t("reason")}
761 value={this.state.removeReason}
762 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
766 class="btn btn-secondary"
767 aria-label={i18n.t("remove_comment")}
769 {i18n.t("remove_comment")}
773 {this.state.showReportDialog && (
776 onSubmit={linkEvent(this, this.handleReportSubmit)}
778 <label class="sr-only" htmlFor={`report-reason-${cv.comment.id}`}>
784 id={`report-reason-${cv.comment.id}`}
785 class="form-control mr-2"
786 placeholder={i18n.t("reason")}
787 value={this.state.reportReason}
788 onInput={linkEvent(this, this.handleReportReasonChange)}
792 class="btn btn-secondary"
793 aria-label={i18n.t("create_report")}
795 {i18n.t("create_report")}
799 {this.state.showBanDialog && (
800 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
801 <div class="form-group row col-12">
803 class="col-form-label"
804 htmlFor={`mod-ban-reason-${cv.comment.id}`}
810 id={`mod-ban-reason-${cv.comment.id}`}
811 class="form-control mr-2"
812 placeholder={i18n.t("reason")}
813 value={this.state.banReason}
814 onInput={linkEvent(this, this.handleModBanReasonChange)}
817 class="col-form-label"
818 htmlFor={`mod-ban-expires-${cv.comment.id}`}
824 id={`mod-ban-expires-${cv.comment.id}`}
825 class="form-control mr-2"
826 placeholder={i18n.t("number_of_days")}
827 value={this.state.banExpireDays}
828 onInput={linkEvent(this, this.handleModBanExpireDaysChange)}
830 <div class="form-group">
831 <div class="form-check">
833 class="form-check-input"
834 id="mod-ban-remove-data"
836 checked={this.state.removeData}
837 onChange={linkEvent(this, this.handleModRemoveDataChange)}
840 class="form-check-label"
841 htmlFor="mod-ban-remove-data"
842 title={i18n.t("remove_content_more")}
844 {i18n.t("remove_content")}
849 {/* TODO hold off on expires until later */}
850 {/* <div class="form-group row"> */}
851 {/* <label class="col-form-label">Expires</label> */}
852 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
854 <div class="form-group row">
857 class="btn btn-secondary"
858 aria-label={i18n.t("ban")}
860 {i18n.t("ban")} {cv.creator.name}
865 {this.state.showReply && (
868 onReplyCancel={this.handleReplyCancel}
869 disabled={this.props.locked}
873 {node.children && !this.state.collapsed && (
875 nodes={node.children}
876 locked={this.props.locked}
877 moderators={this.props.moderators}
878 admins={this.props.admins}
879 postCreatorId={this.props.postCreatorId}
880 enableDownvotes={this.props.enableDownvotes}
883 {/* A collapsed clearfix */}
884 {this.state.collapsed && <div class="row col-12"></div>}
889 get commentOrMentionRead() {
890 let cv = this.props.node.comment_view;
891 return this.isPersonMentionType(cv)
892 ? cv.person_mention.read
896 linkBtn(small = false) {
897 let cv = this.props.node.comment_view;
900 className={`btn ${small && "btn-sm"} btn-link btn-animate text-muted`}
901 to={`/post/${cv.post.id}/comment/${cv.comment.id}`}
902 title={this.props.showContext ? i18n.t("show_context") : i18n.t("link")}
904 <Icon icon="link" classes="icon-inline" />
913 get myComment(): boolean {
915 this.props.node.comment_view.creator.id ==
916 UserService.Instance.myUserInfo?.local_user_view.person.id
920 get isMod(): boolean {
922 this.props.moderators &&
924 this.props.moderators.map(m => m.moderator.id),
925 this.props.node.comment_view.creator.id
930 get isAdmin(): boolean {
934 this.props.admins.map(a => a.person.id),
935 this.props.node.comment_view.creator.id
940 get isPostCreator(): boolean {
941 return this.props.node.comment_view.creator.id == this.props.postCreatorId;
944 get canMod(): boolean {
945 if (this.props.admins && this.props.moderators) {
946 let adminsThenMods = this.props.admins
947 .map(a => a.person.id)
948 .concat(this.props.moderators.map(m => m.moderator.id));
951 UserService.Instance.myUserInfo,
953 this.props.node.comment_view.creator.id
960 get canAdmin(): boolean {
964 UserService.Instance.myUserInfo,
965 this.props.admins.map(a => a.person.id),
966 this.props.node.comment_view.creator.id
971 get amCommunityCreator(): boolean {
973 this.props.moderators &&
974 UserService.Instance.myUserInfo &&
975 this.props.node.comment_view.creator.id !=
976 UserService.Instance.myUserInfo.local_user_view.person.id &&
977 UserService.Instance.myUserInfo.local_user_view.person.id ==
978 this.props.moderators[0].moderator.id
982 get amSiteCreator(): boolean {
985 UserService.Instance.myUserInfo &&
986 this.props.node.comment_view.creator.id !=
987 UserService.Instance.myUserInfo.local_user_view.person.id &&
988 UserService.Instance.myUserInfo.local_user_view.person.id ==
989 this.props.admins[0].person.id
993 get commentUnlessRemoved(): string {
994 let comment = this.props.node.comment_view.comment;
995 return comment.removed
996 ? `*${i18n.t("removed")}*`
998 ? `*${i18n.t("deleted")}*`
1002 handleReplyClick(i: CommentNode) {
1003 i.state.showReply = true;
1004 i.setState(i.state);
1007 handleEditClick(i: CommentNode) {
1008 i.state.showEdit = true;
1009 i.setState(i.state);
1012 handleBlockUserClick(i: CommentNode) {
1013 let blockUserForm: BlockPerson = {
1014 person_id: i.props.node.comment_view.creator.id,
1018 WebSocketService.Instance.send(wsClient.blockPerson(blockUserForm));
1021 handleDeleteClick(i: CommentNode) {
1022 let comment = i.props.node.comment_view.comment;
1023 let deleteForm: DeleteComment = {
1024 comment_id: comment.id,
1025 deleted: !comment.deleted,
1028 WebSocketService.Instance.send(wsClient.deleteComment(deleteForm));
1031 handleSaveCommentClick(i: CommentNode) {
1032 let cv = i.props.node.comment_view;
1033 let save = cv.saved == undefined ? true : !cv.saved;
1034 let form: SaveComment = {
1035 comment_id: cv.comment.id,
1040 WebSocketService.Instance.send(wsClient.saveComment(form));
1042 i.state.saveLoading = true;
1043 i.setState(this.state);
1046 handleReplyCancel() {
1047 this.state.showReply = false;
1048 this.state.showEdit = false;
1049 this.setState(this.state);
1052 handleCommentUpvote(i: CommentNodeI, event: any) {
1053 event.preventDefault();
1054 let new_vote = this.state.my_vote == 1 ? 0 : 1;
1056 if (this.state.my_vote == 1) {
1058 this.state.upvotes--;
1059 } else if (this.state.my_vote == -1) {
1060 this.state.downvotes--;
1061 this.state.upvotes++;
1062 this.state.score += 2;
1064 this.state.upvotes++;
1068 this.state.my_vote = new_vote;
1070 let form: CreateCommentLike = {
1071 comment_id: i.comment_view.comment.id,
1072 score: this.state.my_vote,
1076 WebSocketService.Instance.send(wsClient.likeComment(form));
1077 this.setState(this.state);
1081 handleCommentDownvote(i: CommentNodeI, event: any) {
1082 event.preventDefault();
1083 let new_vote = this.state.my_vote == -1 ? 0 : -1;
1085 if (this.state.my_vote == 1) {
1086 this.state.score -= 2;
1087 this.state.upvotes--;
1088 this.state.downvotes++;
1089 } else if (this.state.my_vote == -1) {
1090 this.state.downvotes--;
1093 this.state.downvotes++;
1097 this.state.my_vote = new_vote;
1099 let form: CreateCommentLike = {
1100 comment_id: i.comment_view.comment.id,
1101 score: this.state.my_vote,
1105 WebSocketService.Instance.send(wsClient.likeComment(form));
1106 this.setState(this.state);
1110 handleShowReportDialog(i: CommentNode) {
1111 i.state.showReportDialog = !i.state.showReportDialog;
1112 i.setState(i.state);
1115 handleReportReasonChange(i: CommentNode, event: any) {
1116 i.state.reportReason = event.target.value;
1117 i.setState(i.state);
1120 handleReportSubmit(i: CommentNode) {
1121 let comment = i.props.node.comment_view.comment;
1122 let form: CreateCommentReport = {
1123 comment_id: comment.id,
1124 reason: i.state.reportReason,
1127 WebSocketService.Instance.send(wsClient.createCommentReport(form));
1129 i.state.showReportDialog = false;
1130 i.setState(i.state);
1133 handleModRemoveShow(i: CommentNode) {
1134 i.state.showRemoveDialog = !i.state.showRemoveDialog;
1135 i.state.showBanDialog = false;
1136 i.setState(i.state);
1139 handleModRemoveReasonChange(i: CommentNode, event: any) {
1140 i.state.removeReason = event.target.value;
1141 i.setState(i.state);
1144 handleModRemoveDataChange(i: CommentNode, event: any) {
1145 i.state.removeData = event.target.checked;
1146 i.setState(i.state);
1149 handleModRemoveSubmit(i: CommentNode) {
1150 let comment = i.props.node.comment_view.comment;
1151 let form: RemoveComment = {
1152 comment_id: comment.id,
1153 removed: !comment.removed,
1154 reason: i.state.removeReason,
1157 WebSocketService.Instance.send(wsClient.removeComment(form));
1159 i.state.showRemoveDialog = false;
1160 i.setState(i.state);
1163 isPersonMentionType(
1164 item: CommentView | PersonMentionView
1165 ): item is PersonMentionView {
1166 return (item as PersonMentionView).person_mention?.id !== undefined;
1169 handleMarkRead(i: CommentNode) {
1170 if (i.isPersonMentionType(i.props.node.comment_view)) {
1171 let form: MarkPersonMentionAsRead = {
1172 person_mention_id: i.props.node.comment_view.person_mention.id,
1173 read: !i.props.node.comment_view.person_mention.read,
1176 WebSocketService.Instance.send(wsClient.markPersonMentionAsRead(form));
1178 let form: MarkCommentAsRead = {
1179 comment_id: i.props.node.comment_view.comment.id,
1180 read: !i.props.node.comment_view.comment.read,
1183 WebSocketService.Instance.send(wsClient.markCommentAsRead(form));
1186 i.state.readLoading = true;
1187 i.setState(this.state);
1190 handleModBanFromCommunityShow(i: CommentNode) {
1191 i.state.showBanDialog = true;
1192 i.state.banType = BanType.Community;
1193 i.state.showRemoveDialog = false;
1194 i.setState(i.state);
1197 handleModBanShow(i: CommentNode) {
1198 i.state.showBanDialog = true;
1199 i.state.banType = BanType.Site;
1200 i.state.showRemoveDialog = false;
1201 i.setState(i.state);
1204 handleModBanReasonChange(i: CommentNode, event: any) {
1205 i.state.banReason = event.target.value;
1206 i.setState(i.state);
1209 handleModBanExpireDaysChange(i: CommentNode, event: any) {
1210 i.state.banExpireDays = event.target.value;
1211 i.setState(i.state);
1214 handleModBanFromCommunitySubmit(i: CommentNode) {
1215 i.state.banType = BanType.Community;
1216 i.setState(i.state);
1217 i.handleModBanBothSubmit(i);
1220 handleModBanSubmit(i: CommentNode) {
1221 i.state.banType = BanType.Site;
1222 i.setState(i.state);
1223 i.handleModBanBothSubmit(i);
1226 handleModBanBothSubmit(i: CommentNode) {
1227 let cv = i.props.node.comment_view;
1229 if (i.state.banType == BanType.Community) {
1230 // If its an unban, restore all their data
1231 let ban = !cv.creator_banned_from_community;
1233 i.state.removeData = false;
1235 let form: BanFromCommunity = {
1236 person_id: cv.creator.id,
1237 community_id: cv.community.id,
1239 remove_data: i.state.removeData,
1240 reason: i.state.banReason,
1241 expires: futureDaysToUnixTime(i.state.banExpireDays),
1244 WebSocketService.Instance.send(wsClient.banFromCommunity(form));
1246 // If its an unban, restore all their data
1247 let ban = !cv.creator.banned;
1249 i.state.removeData = false;
1251 let form: BanPerson = {
1252 person_id: cv.creator.id,
1254 remove_data: i.state.removeData,
1255 reason: i.state.banReason,
1256 expires: futureDaysToUnixTime(i.state.banExpireDays),
1259 WebSocketService.Instance.send(wsClient.banPerson(form));
1262 i.state.showBanDialog = false;
1263 i.setState(i.state);
1266 handleShowConfirmAppointAsMod(i: CommentNode) {
1267 i.state.showConfirmAppointAsMod = true;
1268 i.setState(i.state);
1271 handleCancelConfirmAppointAsMod(i: CommentNode) {
1272 i.state.showConfirmAppointAsMod = false;
1273 i.setState(i.state);
1276 handleAddModToCommunity(i: CommentNode) {
1277 let cv = i.props.node.comment_view;
1278 let form: AddModToCommunity = {
1279 person_id: cv.creator.id,
1280 community_id: cv.community.id,
1284 WebSocketService.Instance.send(wsClient.addModToCommunity(form));
1285 i.state.showConfirmAppointAsMod = false;
1286 i.setState(i.state);
1289 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1290 i.state.showConfirmAppointAsAdmin = true;
1291 i.setState(i.state);
1294 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1295 i.state.showConfirmAppointAsAdmin = false;
1296 i.setState(i.state);
1299 handleAddAdmin(i: CommentNode) {
1300 let form: AddAdmin = {
1301 person_id: i.props.node.comment_view.creator.id,
1305 WebSocketService.Instance.send(wsClient.addAdmin(form));
1306 i.state.showConfirmAppointAsAdmin = false;
1307 i.setState(i.state);
1310 handleShowConfirmTransferCommunity(i: CommentNode) {
1311 i.state.showConfirmTransferCommunity = true;
1312 i.setState(i.state);
1315 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1316 i.state.showConfirmTransferCommunity = false;
1317 i.setState(i.state);
1320 handleTransferCommunity(i: CommentNode) {
1321 let cv = i.props.node.comment_view;
1322 let form: TransferCommunity = {
1323 community_id: cv.community.id,
1324 person_id: cv.creator.id,
1327 WebSocketService.Instance.send(wsClient.transferCommunity(form));
1328 i.state.showConfirmTransferCommunity = false;
1329 i.setState(i.state);
1332 handleShowConfirmTransferSite(i: CommentNode) {
1333 i.state.showConfirmTransferSite = true;
1334 i.setState(i.state);
1337 handleCancelShowConfirmTransferSite(i: CommentNode) {
1338 i.state.showConfirmTransferSite = false;
1339 i.setState(i.state);
1342 handleTransferSite(i: CommentNode) {
1343 let form: TransferSite = {
1344 person_id: i.props.node.comment_view.creator.id,
1347 WebSocketService.Instance.send(wsClient.transferSite(form));
1348 i.state.showConfirmTransferSite = false;
1349 i.setState(i.state);
1352 get isCommentNew(): boolean {
1353 let now = moment.utc().subtract(10, "minutes");
1354 let then = moment.utc(this.props.node.comment_view.comment.published);
1355 return now.isBefore(then);
1358 handleCommentCollapse(i: CommentNode) {
1359 i.state.collapsed = !i.state.collapsed;
1360 i.setState(i.state);
1364 handleViewSource(i: CommentNode) {
1365 i.state.viewSource = !i.state.viewSource;
1366 i.setState(i.state);
1369 handleShowAdvanced(i: CommentNode) {
1370 i.state.showAdvanced = !i.state.showAdvanced;
1371 i.setState(i.state);
1376 if (this.state.my_vote == 1) {
1378 } else if (this.state.my_vote == -1) {
1379 return "text-danger";
1381 return "text-muted";
1385 get pointsTippy(): string {
1386 let points = i18n.t("number_of_points", {
1387 count: this.state.score,
1388 formattedCount: this.state.score,
1391 let upvotes = i18n.t("number_of_upvotes", {
1392 count: this.state.upvotes,
1393 formattedCount: this.state.upvotes,
1396 let downvotes = i18n.t("number_of_downvotes", {
1397 count: this.state.downvotes,
1398 formattedCount: this.state.downvotes,
1401 return `${points} • ${upvotes} • ${downvotes}`;
1404 get expandText(): string {
1405 return this.state.collapsed ? i18n.t("expand") : i18n.t("collapse");