1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
4 CommentNode as CommentNodeI,
6 CommentForm as CommentFormI,
13 AddModToCommunityForm,
15 TransferCommunityForm,
20 } from '../interfaces';
21 import { WebSocketService, UserService } from '../services';
30 import moment from 'moment';
31 import { MomentTime } from './moment-time';
32 import { CommentForm } from './comment-form';
33 import { CommentNodes } from './comment-nodes';
34 import { UserListing } from './user-listing';
35 import { i18n } from '../i18next';
36 import { CakeDay } from './cake-day';
38 interface CommentNodeState {
41 showRemoveDialog: boolean;
43 showBanDialog: boolean;
47 showConfirmTransferSite: boolean;
48 showConfirmTransferCommunity: boolean;
49 showConfirmAppointAsMod: boolean;
50 showConfirmAppointAsAdmin: boolean;
53 showAdvanced: boolean;
63 interface CommentNodeProps {
69 showContext?: boolean;
70 moderators: Array<CommunityUser>;
71 admins: Array<UserView>;
72 // TODO is this necessary, can't I get it from the node itself?
73 postCreatorId?: number;
74 showCommunity?: boolean;
75 sort?: CommentSortType;
77 enableDownvotes: boolean;
80 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
81 private emptyState: CommentNodeState = {
84 showRemoveDialog: false,
89 banType: BanType.Community,
93 showConfirmTransferSite: false,
94 showConfirmTransferCommunity: false,
95 showConfirmAppointAsMod: false,
96 showConfirmAppointAsAdmin: false,
97 my_vote: this.props.node.comment.my_vote,
98 score: this.props.node.comment.score,
99 upvotes: this.props.node.comment.upvotes,
100 downvotes: this.props.node.comment.downvotes,
101 borderColor: this.props.node.comment.depth
102 ? colorList[this.props.node.comment.depth % colorList.length]
108 constructor(props: any, context: any) {
109 super(props, context);
111 this.state = this.emptyState;
112 this.handleReplyCancel = this.handleReplyCancel.bind(this);
113 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
114 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
117 componentWillReceiveProps(nextProps: CommentNodeProps) {
118 this.state.my_vote = nextProps.node.comment.my_vote;
119 this.state.upvotes = nextProps.node.comment.upvotes;
120 this.state.downvotes = nextProps.node.comment.downvotes;
121 this.state.score = nextProps.node.comment.score;
122 this.state.readLoading = false;
123 this.state.saveLoading = false;
124 this.setState(this.state);
128 let node = this.props.node;
129 const { creator_name, creator_published } = node.comment;
132 className={`comment ${
133 node.comment.parent_id && !this.props.noIndent ? 'ml-1' : ''
137 id={`comment-${node.comment.id}`}
138 className={`details comment-node border-top border-light py-2 ${
139 this.isCommentNew ? 'mark' : ''
142 !this.props.noIndent &&
143 this.props.node.comment.parent_id &&
144 `border-left: 2px ${this.state.borderColor} solid !important`
149 !this.props.noIndent &&
150 this.props.node.comment.parent_id &&
154 <div class="d-flex flex-wrap align-items-center text-muted small">
158 name: node.comment.creator_name,
159 avatar: node.comment.creator_avatar,
160 id: node.comment.creator_id,
161 local: node.comment.creator_local,
162 actor_id: node.comment.creator_actor_id,
168 creator_name={creator_name}
169 creator_published={creator_published}
173 <div className="badge badge-light d-none d-sm-inline mr-2">
178 <div className="badge badge-light d-none d-sm-inline mr-2">
182 {this.isPostCreator && (
183 <div className="badge badge-light d-none d-sm-inline mr-2">
187 {(node.comment.banned_from_community || node.comment.banned) && (
188 <div className="badge badge-danger mr-2">
192 {this.props.showCommunity && (
194 <span class="mx-1">{i18n.t('to')}</span>
195 <Link class="mr-2" to={`/c/${node.comment.community_name}`}>
196 {node.comment.community_name}
201 className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"
202 onClick={linkEvent(this, this.handleCommentCollapse)}
204 {this.state.collapsed ? (
205 <svg class="icon icon-inline">
206 <use xlinkHref="#icon-plus-square"></use>
209 <svg class="icon icon-inline">
210 <use xlinkHref="#icon-minus-square"></use>
215 className={`unselectable pointer ${this.scoreColor}`}
216 onClick={linkEvent(node, this.handleCommentUpvote)}
217 data-tippy-content={this.pointsTippy}
219 <svg class="icon icon-inline mr-1">
220 <use xlinkHref="#icon-zap"></use>
222 <span class="mr-1">{this.state.score}</span>
224 <span className="mr-1">•</span>
226 <MomentTime data={node.comment} />
229 {/* end of user row */}
230 {this.state.showEdit && (
234 onReplyCancel={this.handleReplyCancel}
235 disabled={this.props.locked}
238 {!this.state.showEdit && !this.state.collapsed && (
240 {this.state.viewSource ? (
241 <pre>{this.commentUnlessRemoved}</pre>
245 dangerouslySetInnerHTML={mdToHtml(
246 this.commentUnlessRemoved
250 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
251 {this.props.showContext && this.linkBtn}
252 {this.props.markable && (
254 class="btn btn-link btn-animate text-muted"
255 onClick={linkEvent(this, this.handleMarkRead)}
258 ? i18n.t('mark_as_unread')
259 : i18n.t('mark_as_read')
262 {this.state.readLoading ? (
266 class={`icon icon-inline ${
267 node.comment.read && 'text-success'
270 <use xlinkHref="#icon-check"></use>
275 {UserService.Instance.user && !this.props.viewOnly && (
278 className={`btn btn-link btn-animate ${
279 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
281 onClick={linkEvent(node, this.handleCommentUpvote)}
282 data-tippy-content={i18n.t('upvote')}
284 <svg class="icon icon-inline">
285 <use xlinkHref="#icon-arrow-up"></use>
287 {this.state.upvotes !== this.state.score && (
288 <span class="ml-1">{this.state.upvotes}</span>
291 {this.props.enableDownvotes && (
293 className={`btn btn-link btn-animate ${
294 this.state.my_vote == -1
298 onClick={linkEvent(node, this.handleCommentDownvote)}
299 data-tippy-content={i18n.t('downvote')}
301 <svg class="icon icon-inline">
302 <use xlinkHref="#icon-arrow-down"></use>
304 {this.state.upvotes !== this.state.score && (
305 <span class="ml-1">{this.state.downvotes}</span>
310 class="btn btn-link btn-animate text-muted"
311 onClick={linkEvent(this, this.handleReplyClick)}
312 data-tippy-content={i18n.t('reply')}
314 <svg class="icon icon-inline">
315 <use xlinkHref="#icon-reply1"></use>
318 {!this.state.showAdvanced ? (
320 className="btn btn-link btn-animate text-muted"
321 onClick={linkEvent(this, this.handleShowAdvanced)}
322 data-tippy-content={i18n.t('more')}
324 <svg class="icon icon-inline">
325 <use xlinkHref="#icon-more-vertical"></use>
330 {!this.myComment && (
331 <button class="btn btn-link btn-animate">
334 to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
335 title={i18n.t('message').toLowerCase()}
338 <use xlinkHref="#icon-mail"></use>
343 {!this.props.showContext && this.linkBtn}
345 class="btn btn-link btn-animate text-muted"
348 this.handleSaveCommentClick
356 {this.state.saveLoading ? (
360 class={`icon icon-inline ${
361 node.comment.saved && 'text-warning'
364 <use xlinkHref="#icon-star"></use>
369 className="btn btn-link btn-animate text-muted"
370 onClick={linkEvent(this, this.handleViewSource)}
371 data-tippy-content={i18n.t('view_source')}
374 class={`icon icon-inline ${
375 this.state.viewSource && 'text-success'
378 <use xlinkHref="#icon-file-text"></use>
384 class="btn btn-link btn-animate text-muted"
385 onClick={linkEvent(this, this.handleEditClick)}
386 data-tippy-content={i18n.t('edit')}
388 <svg class="icon icon-inline">
389 <use xlinkHref="#icon-edit"></use>
393 class="btn btn-link btn-animate text-muted"
396 this.handleDeleteClick
399 !node.comment.deleted
405 class={`icon icon-inline ${
406 node.comment.deleted && 'text-danger'
409 <use xlinkHref="#icon-trash"></use>
414 {/* Admins and mods can remove comments */}
415 {(this.canMod || this.canAdmin) && (
417 {!node.comment.removed ? (
419 class="btn btn-link btn-animate text-muted"
422 this.handleModRemoveShow
429 class="btn btn-link btn-animate text-muted"
432 this.handleModRemoveSubmit
440 {/* Mods can ban from community, and appoint as mods to community */}
444 (!node.comment.banned_from_community ? (
446 class="btn btn-link btn-animate text-muted"
449 this.handleModBanFromCommunityShow
456 class="btn btn-link btn-animate text-muted"
459 this.handleModBanFromCommunitySubmit
465 {!node.comment.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 <button class="btn btn-link btn-animate text-muted">
481 {i18n.t('are_you_sure')}
484 class="btn btn-link btn-animate text-muted"
487 this.handleAddModToCommunity
493 class="btn btn-link btn-animate text-muted"
496 this.handleCancelConfirmAppointAsMod
505 {/* Community creators and admins can transfer community to another mod */}
506 {(this.amCommunityCreator || this.canAdmin) &&
508 (!this.state.showConfirmTransferCommunity ? (
510 class="btn btn-link btn-animate text-muted"
513 this.handleShowConfirmTransferCommunity
516 {i18n.t('transfer_community')}
520 <button class="btn btn-link btn-animate text-muted">
521 {i18n.t('are_you_sure')}
524 class="btn btn-link btn-animate text-muted"
527 this.handleTransferCommunity
533 class="btn btn-link btn-animate text-muted"
537 .handleCancelShowConfirmTransferCommunity
544 {/* Admins can ban from all, and appoint other admins */}
548 (!node.comment.banned ? (
550 class="btn btn-link btn-animate text-muted"
553 this.handleModBanShow
556 {i18n.t('ban_from_site')}
560 class="btn btn-link btn-animate text-muted"
563 this.handleModBanSubmit
566 {i18n.t('unban_from_site')}
569 {!node.comment.banned &&
570 (!this.state.showConfirmAppointAsAdmin ? (
572 class="btn btn-link btn-animate text-muted"
575 this.handleShowConfirmAppointAsAdmin
579 ? i18n.t('remove_as_admin')
580 : i18n.t('appoint_as_admin')}
584 <button class="btn btn-link btn-animate text-muted">
585 {i18n.t('are_you_sure')}
588 class="btn btn-link btn-animate text-muted"
597 class="btn btn-link btn-animate text-muted"
600 this.handleCancelConfirmAppointAsAdmin
609 {/* Site Creator can transfer to another admin */}
610 {this.amSiteCreator &&
612 (!this.state.showConfirmTransferSite ? (
614 class="btn btn-link btn-animate text-muted"
617 this.handleShowConfirmTransferSite
620 {i18n.t('transfer_site')}
624 <button class="btn btn-link btn-animate text-muted">
625 {i18n.t('are_you_sure')}
628 class="btn btn-link btn-animate text-muted"
631 this.handleTransferSite
637 class="btn btn-link btn-animate text-muted"
640 this.handleCancelShowConfirmTransferSite
652 {/* end of button group */}
657 {/* end of details */}
658 {this.state.showRemoveDialog && (
661 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
665 class="form-control mr-2"
666 placeholder={i18n.t('reason')}
667 value={this.state.removeReason}
668 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
670 <button type="submit" class="btn btn-secondary">
671 {i18n.t('remove_comment')}
675 {this.state.showBanDialog && (
676 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
677 <div class="form-group row">
678 <label class="col-form-label">{i18n.t('reason')}</label>
681 class="form-control mr-2"
682 placeholder={i18n.t('reason')}
683 value={this.state.banReason}
684 onInput={linkEvent(this, this.handleModBanReasonChange)}
687 {/* TODO hold off on expires until later */}
688 {/* <div class="form-group row"> */}
689 {/* <label class="col-form-label">Expires</label> */}
690 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
692 <div class="form-group row">
693 <button type="submit" class="btn btn-secondary">
694 {i18n.t('ban')} {node.comment.creator_name}
699 {this.state.showReply && (
702 onReplyCancel={this.handleReplyCancel}
703 disabled={this.props.locked}
706 {node.children && !this.state.collapsed && (
708 nodes={node.children}
709 locked={this.props.locked}
710 moderators={this.props.moderators}
711 admins={this.props.admins}
712 postCreatorId={this.props.postCreatorId}
713 sort={this.props.sort}
714 sortType={this.props.sortType}
715 enableDownvotes={this.props.enableDownvotes}
718 {/* A collapsed clearfix */}
719 {this.state.collapsed && <div class="row col-12"></div>}
725 let node = this.props.node;
728 class="btn btn-link btn-animate text-muted"
729 to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
730 title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')}
732 <svg class="icon icon-inline">
733 <use xlinkHref="#icon-link"></use>
741 <svg class="icon icon-spinner spin">
742 <use xlinkHref="#icon-spinner"></use>
747 get myComment(): boolean {
749 UserService.Instance.user &&
750 this.props.node.comment.creator_id == UserService.Instance.user.id
754 get isMod(): boolean {
756 this.props.moderators &&
758 this.props.moderators.map(m => m.user_id),
759 this.props.node.comment.creator_id
764 get isAdmin(): boolean {
768 this.props.admins.map(a => a.id),
769 this.props.node.comment.creator_id
774 get isPostCreator(): boolean {
775 return this.props.node.comment.creator_id == this.props.postCreatorId;
778 get canMod(): boolean {
779 if (this.props.admins && this.props.moderators) {
780 let adminsThenMods = this.props.admins
782 .concat(this.props.moderators.map(m => m.user_id));
785 UserService.Instance.user,
787 this.props.node.comment.creator_id
794 get canAdmin(): boolean {
798 UserService.Instance.user,
799 this.props.admins.map(a => a.id),
800 this.props.node.comment.creator_id
805 get amCommunityCreator(): boolean {
807 this.props.moderators &&
808 UserService.Instance.user &&
809 this.props.node.comment.creator_id != UserService.Instance.user.id &&
810 UserService.Instance.user.id == this.props.moderators[0].user_id
814 get amSiteCreator(): boolean {
817 UserService.Instance.user &&
818 this.props.node.comment.creator_id != UserService.Instance.user.id &&
819 UserService.Instance.user.id == this.props.admins[0].id
823 get commentUnlessRemoved(): string {
824 let node = this.props.node;
825 return node.comment.removed
826 ? `*${i18n.t('removed')}*`
827 : node.comment.deleted
828 ? `*${i18n.t('deleted')}*`
829 : node.comment.content;
832 handleReplyClick(i: CommentNode) {
833 i.state.showReply = true;
837 handleEditClick(i: CommentNode) {
838 i.state.showEdit = true;
842 handleDeleteClick(i: CommentNode) {
843 let deleteForm: CommentFormI = {
844 content: i.props.node.comment.content,
845 edit_id: i.props.node.comment.id,
846 creator_id: i.props.node.comment.creator_id,
847 post_id: i.props.node.comment.post_id,
848 parent_id: i.props.node.comment.parent_id,
849 deleted: !i.props.node.comment.deleted,
852 WebSocketService.Instance.editComment(deleteForm);
855 handleSaveCommentClick(i: CommentNode) {
857 i.props.node.comment.saved == undefined
859 : !i.props.node.comment.saved;
860 let form: SaveCommentForm = {
861 comment_id: i.props.node.comment.id,
865 WebSocketService.Instance.saveComment(form);
867 i.state.saveLoading = true;
868 i.setState(this.state);
871 handleReplyCancel() {
872 this.state.showReply = false;
873 this.state.showEdit = false;
874 this.setState(this.state);
877 handleCommentUpvote(i: CommentNodeI) {
878 let new_vote = this.state.my_vote == 1 ? 0 : 1;
880 if (this.state.my_vote == 1) {
882 this.state.upvotes--;
883 } else if (this.state.my_vote == -1) {
884 this.state.downvotes--;
885 this.state.upvotes++;
886 this.state.score += 2;
888 this.state.upvotes++;
892 this.state.my_vote = new_vote;
894 let form: CommentLikeForm = {
895 comment_id: i.comment.id,
896 post_id: i.comment.post_id,
897 score: this.state.my_vote,
900 WebSocketService.Instance.likeComment(form);
901 this.setState(this.state);
905 handleCommentDownvote(i: CommentNodeI) {
906 let new_vote = this.state.my_vote == -1 ? 0 : -1;
908 if (this.state.my_vote == 1) {
909 this.state.score -= 2;
910 this.state.upvotes--;
911 this.state.downvotes++;
912 } else if (this.state.my_vote == -1) {
913 this.state.downvotes--;
916 this.state.downvotes++;
920 this.state.my_vote = new_vote;
922 let form: CommentLikeForm = {
923 comment_id: i.comment.id,
924 post_id: i.comment.post_id,
925 score: this.state.my_vote,
928 WebSocketService.Instance.likeComment(form);
929 this.setState(this.state);
933 handleModRemoveShow(i: CommentNode) {
934 i.state.showRemoveDialog = true;
938 handleModRemoveReasonChange(i: CommentNode, event: any) {
939 i.state.removeReason = event.target.value;
943 handleModRemoveSubmit(i: CommentNode) {
944 event.preventDefault();
945 let form: CommentFormI = {
946 content: i.props.node.comment.content,
947 edit_id: i.props.node.comment.id,
948 creator_id: i.props.node.comment.creator_id,
949 post_id: i.props.node.comment.post_id,
950 parent_id: i.props.node.comment.parent_id,
951 removed: !i.props.node.comment.removed,
952 reason: i.state.removeReason,
955 WebSocketService.Instance.editComment(form);
957 i.state.showRemoveDialog = false;
961 handleMarkRead(i: CommentNode) {
962 // if it has a user_mention_id field, then its a mention
963 if (i.props.node.comment.user_mention_id) {
964 let form: EditUserMentionForm = {
965 user_mention_id: i.props.node.comment.user_mention_id,
966 read: !i.props.node.comment.read,
968 WebSocketService.Instance.editUserMention(form);
970 let form: CommentFormI = {
971 content: i.props.node.comment.content,
972 edit_id: i.props.node.comment.id,
973 creator_id: i.props.node.comment.creator_id,
974 post_id: i.props.node.comment.post_id,
975 parent_id: i.props.node.comment.parent_id,
976 read: !i.props.node.comment.read,
979 WebSocketService.Instance.editComment(form);
982 i.state.readLoading = true;
983 i.setState(this.state);
986 handleModBanFromCommunityShow(i: CommentNode) {
987 i.state.showBanDialog = !i.state.showBanDialog;
988 i.state.banType = BanType.Community;
992 handleModBanShow(i: CommentNode) {
993 i.state.showBanDialog = !i.state.showBanDialog;
994 i.state.banType = BanType.Site;
998 handleModBanReasonChange(i: CommentNode, event: any) {
999 i.state.banReason = event.target.value;
1000 i.setState(i.state);
1003 handleModBanExpiresChange(i: CommentNode, event: any) {
1004 i.state.banExpires = event.target.value;
1005 i.setState(i.state);
1008 handleModBanFromCommunitySubmit(i: CommentNode) {
1009 i.state.banType = BanType.Community;
1010 i.setState(i.state);
1011 i.handleModBanBothSubmit(i);
1014 handleModBanSubmit(i: CommentNode) {
1015 i.state.banType = BanType.Site;
1016 i.setState(i.state);
1017 i.handleModBanBothSubmit(i);
1020 handleModBanBothSubmit(i: CommentNode) {
1021 event.preventDefault();
1023 if (i.state.banType == BanType.Community) {
1024 let form: BanFromCommunityForm = {
1025 user_id: i.props.node.comment.creator_id,
1026 community_id: i.props.node.comment.community_id,
1027 ban: !i.props.node.comment.banned_from_community,
1028 reason: i.state.banReason,
1029 expires: getUnixTime(i.state.banExpires),
1031 WebSocketService.Instance.banFromCommunity(form);
1033 let form: BanUserForm = {
1034 user_id: i.props.node.comment.creator_id,
1035 ban: !i.props.node.comment.banned,
1036 reason: i.state.banReason,
1037 expires: getUnixTime(i.state.banExpires),
1039 WebSocketService.Instance.banUser(form);
1042 i.state.showBanDialog = false;
1043 i.setState(i.state);
1046 handleShowConfirmAppointAsMod(i: CommentNode) {
1047 i.state.showConfirmAppointAsMod = true;
1048 i.setState(i.state);
1051 handleCancelConfirmAppointAsMod(i: CommentNode) {
1052 i.state.showConfirmAppointAsMod = false;
1053 i.setState(i.state);
1056 handleAddModToCommunity(i: CommentNode) {
1057 let form: AddModToCommunityForm = {
1058 user_id: i.props.node.comment.creator_id,
1059 community_id: i.props.node.comment.community_id,
1062 WebSocketService.Instance.addModToCommunity(form);
1063 i.state.showConfirmAppointAsMod = false;
1064 i.setState(i.state);
1067 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1068 i.state.showConfirmAppointAsAdmin = true;
1069 i.setState(i.state);
1072 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1073 i.state.showConfirmAppointAsAdmin = false;
1074 i.setState(i.state);
1077 handleAddAdmin(i: CommentNode) {
1078 let form: AddAdminForm = {
1079 user_id: i.props.node.comment.creator_id,
1082 WebSocketService.Instance.addAdmin(form);
1083 i.state.showConfirmAppointAsAdmin = false;
1084 i.setState(i.state);
1087 handleShowConfirmTransferCommunity(i: CommentNode) {
1088 i.state.showConfirmTransferCommunity = true;
1089 i.setState(i.state);
1092 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1093 i.state.showConfirmTransferCommunity = false;
1094 i.setState(i.state);
1097 handleTransferCommunity(i: CommentNode) {
1098 let form: TransferCommunityForm = {
1099 community_id: i.props.node.comment.community_id,
1100 user_id: i.props.node.comment.creator_id,
1102 WebSocketService.Instance.transferCommunity(form);
1103 i.state.showConfirmTransferCommunity = false;
1104 i.setState(i.state);
1107 handleShowConfirmTransferSite(i: CommentNode) {
1108 i.state.showConfirmTransferSite = true;
1109 i.setState(i.state);
1112 handleCancelShowConfirmTransferSite(i: CommentNode) {
1113 i.state.showConfirmTransferSite = false;
1114 i.setState(i.state);
1117 handleTransferSite(i: CommentNode) {
1118 let form: TransferSiteForm = {
1119 user_id: i.props.node.comment.creator_id,
1121 WebSocketService.Instance.transferSite(form);
1122 i.state.showConfirmTransferSite = false;
1123 i.setState(i.state);
1126 get isCommentNew(): boolean {
1127 let now = moment.utc().subtract(10, 'minutes');
1128 let then = moment.utc(this.props.node.comment.published);
1129 return now.isBefore(then);
1132 handleCommentCollapse(i: CommentNode) {
1133 i.state.collapsed = !i.state.collapsed;
1134 i.setState(i.state);
1137 handleViewSource(i: CommentNode) {
1138 i.state.viewSource = !i.state.viewSource;
1139 i.setState(i.state);
1142 handleShowAdvanced(i: CommentNode) {
1143 i.state.showAdvanced = !i.state.showAdvanced;
1144 i.setState(i.state);
1149 if (this.state.my_vote == 1) {
1151 } else if (this.state.my_vote == -1) {
1152 return 'text-danger';
1154 return 'text-muted';
1158 get pointsTippy(): string {
1159 let points = i18n.t('number_of_points', {
1160 count: this.state.score,
1163 let upvotes = i18n.t('number_of_upvotes', {
1164 count: this.state.upvotes,
1167 let downvotes = i18n.t('number_of_downvotes', {
1168 count: this.state.downvotes,
1171 return `${points} • ${upvotes} • ${downvotes}`;