1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
4 CommentNode as CommentNodeI,
9 MarkUserMentionAsReadForm,
15 AddModToCommunityForm,
17 TransferCommunityForm,
22 } from '../interfaces';
23 import { WebSocketService, UserService } from '../services';
32 import moment from 'moment';
33 import { MomentTime } from './moment-time';
34 import { CommentForm } from './comment-form';
35 import { CommentNodes } from './comment-nodes';
36 import { UserListing } from './user-listing';
37 import { CommunityLink } from './community-link';
38 import { i18n } from '../i18next';
40 interface CommentNodeState {
43 showRemoveDialog: boolean;
45 showBanDialog: boolean;
49 showConfirmTransferSite: boolean;
50 showConfirmTransferCommunity: boolean;
51 showConfirmAppointAsMod: boolean;
52 showConfirmAppointAsAdmin: boolean;
55 showAdvanced: boolean;
65 interface CommentNodeProps {
72 showContext?: boolean;
73 moderators: Array<CommunityUser>;
74 admins: Array<UserView>;
75 // TODO is this necessary, can't I get it from the node itself?
76 postCreatorId?: number;
77 showCommunity?: boolean;
78 sort?: CommentSortType;
80 enableDownvotes: boolean;
83 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
84 private emptyState: CommentNodeState = {
87 showRemoveDialog: false,
92 banType: BanType.Community,
96 showConfirmTransferSite: false,
97 showConfirmTransferCommunity: false,
98 showConfirmAppointAsMod: false,
99 showConfirmAppointAsAdmin: false,
100 my_vote: this.props.node.comment.my_vote,
101 score: this.props.node.comment.score,
102 upvotes: this.props.node.comment.upvotes,
103 downvotes: this.props.node.comment.downvotes,
104 borderColor: this.props.node.comment.depth
105 ? colorList[this.props.node.comment.depth % colorList.length]
111 constructor(props: any, context: any) {
112 super(props, context);
114 this.state = this.emptyState;
115 this.handleReplyCancel = this.handleReplyCancel.bind(this);
116 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
117 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
120 componentWillReceiveProps(nextProps: CommentNodeProps) {
121 this.state.my_vote = nextProps.node.comment.my_vote;
122 this.state.upvotes = nextProps.node.comment.upvotes;
123 this.state.downvotes = nextProps.node.comment.downvotes;
124 this.state.score = nextProps.node.comment.score;
125 this.state.readLoading = false;
126 this.state.saveLoading = false;
127 this.setState(this.state);
131 let node = this.props.node;
134 className={`comment ${
135 node.comment.parent_id && !this.props.noIndent ? 'ml-1' : ''
139 id={`comment-${node.comment.id}`}
140 className={`details comment-node py-2 ${
141 !this.props.noBorder ? 'border-top border-light' : ''
142 } ${this.isCommentNew ? 'mark' : ''}`}
144 !this.props.noIndent &&
145 this.props.node.comment.parent_id &&
146 `border-left: 2px ${this.state.borderColor} solid !important`
151 !this.props.noIndent &&
152 this.props.node.comment.parent_id &&
156 <div class="d-flex flex-wrap align-items-center text-muted small">
160 name: node.comment.creator_name,
161 preferred_username: node.comment.creator_preferred_username,
162 avatar: node.comment.creator_avatar,
163 id: node.comment.creator_id,
164 local: node.comment.creator_local,
165 actor_id: node.comment.creator_actor_id,
166 published: node.comment.creator_published,
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 {(node.comment.banned_from_community || node.comment.banned) && (
187 <div className="badge badge-danger mr-2">
191 {this.props.showCommunity && (
193 <span class="mx-1">{i18n.t('to')}</span>
196 name: node.comment.community_name,
197 id: node.comment.community_id,
198 local: node.comment.community_local,
199 actor_id: node.comment.community_actor_id,
200 icon: node.comment.community_icon,
203 <span class="mx-2">•</span>
204 <Link class="mr-2" to={`/post/${node.comment.post_id}`}>
205 {node.comment.post_name}
210 class="btn text-muted"
211 onClick={linkEvent(this, this.handleCommentCollapse)}
213 {this.state.collapsed ? (
214 <svg class="icon icon-inline">
215 <use xlinkHref="#icon-plus-square"></use>
218 <svg class="icon icon-inline">
219 <use xlinkHref="#icon-minus-square"></use>
223 {/* This is an expanding spacer for mobile */}
224 <div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
226 className={`btn p-0 unselectable pointer ${this.scoreColor}`}
227 onClick={linkEvent(node, this.handleCommentUpvote)}
228 data-tippy-content={this.pointsTippy}
230 <svg class="icon icon-inline mr-1">
231 <use xlinkHref="#icon-zap"></use>
233 <span class="mr-1">{this.state.score}</span>
235 <span className="mr-1">•</span>
237 <MomentTime data={node.comment} />
240 {/* end of user row */}
241 {this.state.showEdit && (
245 onReplyCancel={this.handleReplyCancel}
246 disabled={this.props.locked}
250 {!this.state.showEdit && !this.state.collapsed && (
252 {this.state.viewSource ? (
253 <pre>{this.commentUnlessRemoved}</pre>
257 dangerouslySetInnerHTML={mdToHtml(
258 this.commentUnlessRemoved
262 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
263 {this.props.showContext && this.linkBtn}
264 {this.props.markable && (
266 class="btn btn-link btn-animate text-muted"
267 onClick={linkEvent(this, this.handleMarkRead)}
270 ? i18n.t('mark_as_unread')
271 : i18n.t('mark_as_read')
274 {this.state.readLoading ? (
278 class={`icon icon-inline ${
279 node.comment.read && 'text-success'
282 <use xlinkHref="#icon-check"></use>
287 {UserService.Instance.user && !this.props.viewOnly && (
290 className={`btn btn-link btn-animate ${
291 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
293 onClick={linkEvent(node, this.handleCommentUpvote)}
294 data-tippy-content={i18n.t('upvote')}
296 <svg class="icon icon-inline">
297 <use xlinkHref="#icon-arrow-up"></use>
299 {this.state.upvotes !== this.state.score && (
300 <span class="ml-1">{this.state.upvotes}</span>
303 {this.props.enableDownvotes && (
305 className={`btn btn-link btn-animate ${
306 this.state.my_vote == -1
310 onClick={linkEvent(node, this.handleCommentDownvote)}
311 data-tippy-content={i18n.t('downvote')}
313 <svg class="icon icon-inline">
314 <use xlinkHref="#icon-arrow-down"></use>
316 {this.state.upvotes !== this.state.score && (
317 <span class="ml-1">{this.state.downvotes}</span>
322 class="btn btn-link btn-animate text-muted"
323 onClick={linkEvent(this, this.handleReplyClick)}
324 data-tippy-content={i18n.t('reply')}
326 <svg class="icon icon-inline">
327 <use xlinkHref="#icon-reply1"></use>
330 {!this.state.showAdvanced ? (
332 className="btn btn-link btn-animate text-muted"
333 onClick={linkEvent(this, this.handleShowAdvanced)}
334 data-tippy-content={i18n.t('more')}
336 <svg class="icon icon-inline">
337 <use xlinkHref="#icon-more-vertical"></use>
342 {!this.myComment && (
343 <button class="btn btn-link btn-animate">
346 to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
347 title={i18n.t('message').toLowerCase()}
350 <use xlinkHref="#icon-mail"></use>
355 {!this.props.showContext && this.linkBtn}
357 class="btn btn-link btn-animate text-muted"
360 this.handleSaveCommentClick
368 {this.state.saveLoading ? (
372 class={`icon icon-inline ${
373 node.comment.saved && 'text-warning'
376 <use xlinkHref="#icon-star"></use>
381 className="btn btn-link btn-animate text-muted"
382 onClick={linkEvent(this, this.handleViewSource)}
383 data-tippy-content={i18n.t('view_source')}
386 class={`icon icon-inline ${
387 this.state.viewSource && 'text-success'
390 <use xlinkHref="#icon-file-text"></use>
396 class="btn btn-link btn-animate text-muted"
397 onClick={linkEvent(this, this.handleEditClick)}
398 data-tippy-content={i18n.t('edit')}
400 <svg class="icon icon-inline">
401 <use xlinkHref="#icon-edit"></use>
405 class="btn btn-link btn-animate text-muted"
408 this.handleDeleteClick
411 !node.comment.deleted
417 class={`icon icon-inline ${
418 node.comment.deleted && 'text-danger'
421 <use xlinkHref="#icon-trash"></use>
426 {/* Admins and mods can remove comments */}
427 {(this.canMod || this.canAdmin) && (
429 {!node.comment.removed ? (
431 class="btn btn-link btn-animate text-muted"
434 this.handleModRemoveShow
441 class="btn btn-link btn-animate text-muted"
444 this.handleModRemoveSubmit
452 {/* Mods can ban from community, and appoint as mods to community */}
456 (!node.comment.banned_from_community ? (
458 class="btn btn-link btn-animate text-muted"
461 this.handleModBanFromCommunityShow
468 class="btn btn-link btn-animate text-muted"
471 this.handleModBanFromCommunitySubmit
477 {!node.comment.banned_from_community &&
478 node.comment.creator_local &&
479 (!this.state.showConfirmAppointAsMod ? (
481 class="btn btn-link btn-animate text-muted"
484 this.handleShowConfirmAppointAsMod
488 ? i18n.t('remove_as_mod')
489 : i18n.t('appoint_as_mod')}
493 <button class="btn btn-link btn-animate text-muted">
494 {i18n.t('are_you_sure')}
497 class="btn btn-link btn-animate text-muted"
500 this.handleAddModToCommunity
506 class="btn btn-link btn-animate text-muted"
509 this.handleCancelConfirmAppointAsMod
518 {/* Community creators and admins can transfer community to another mod */}
519 {(this.amCommunityCreator || this.canAdmin) &&
521 node.comment.creator_local &&
522 (!this.state.showConfirmTransferCommunity ? (
524 class="btn btn-link btn-animate text-muted"
527 this.handleShowConfirmTransferCommunity
530 {i18n.t('transfer_community')}
534 <button class="btn btn-link btn-animate text-muted">
535 {i18n.t('are_you_sure')}
538 class="btn btn-link btn-animate text-muted"
541 this.handleTransferCommunity
547 class="btn btn-link btn-animate text-muted"
551 .handleCancelShowConfirmTransferCommunity
558 {/* Admins can ban from all, and appoint other admins */}
562 (!node.comment.banned ? (
564 class="btn btn-link btn-animate text-muted"
567 this.handleModBanShow
570 {i18n.t('ban_from_site')}
574 class="btn btn-link btn-animate text-muted"
577 this.handleModBanSubmit
580 {i18n.t('unban_from_site')}
583 {!node.comment.banned &&
584 node.comment.creator_local &&
585 (!this.state.showConfirmAppointAsAdmin ? (
587 class="btn btn-link btn-animate text-muted"
590 this.handleShowConfirmAppointAsAdmin
594 ? i18n.t('remove_as_admin')
595 : i18n.t('appoint_as_admin')}
599 <button class="btn btn-link btn-animate text-muted">
600 {i18n.t('are_you_sure')}
603 class="btn btn-link btn-animate text-muted"
612 class="btn btn-link btn-animate text-muted"
615 this.handleCancelConfirmAppointAsAdmin
624 {/* Site Creator can transfer to another admin */}
625 {this.amSiteCreator &&
627 node.comment.creator_local &&
628 (!this.state.showConfirmTransferSite ? (
630 class="btn btn-link btn-animate text-muted"
633 this.handleShowConfirmTransferSite
636 {i18n.t('transfer_site')}
640 <button class="btn btn-link btn-animate text-muted">
641 {i18n.t('are_you_sure')}
644 class="btn btn-link btn-animate text-muted"
647 this.handleTransferSite
653 class="btn btn-link btn-animate text-muted"
656 this.handleCancelShowConfirmTransferSite
668 {/* end of button group */}
673 {/* end of details */}
674 {this.state.showRemoveDialog && (
677 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
681 class="form-control mr-2"
682 placeholder={i18n.t('reason')}
683 value={this.state.removeReason}
684 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
686 <button type="submit" class="btn btn-secondary">
687 {i18n.t('remove_comment')}
691 {this.state.showBanDialog && (
692 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
693 <div class="form-group row">
694 <label class="col-form-label">{i18n.t('reason')}</label>
697 class="form-control mr-2"
698 placeholder={i18n.t('reason')}
699 value={this.state.banReason}
700 onInput={linkEvent(this, this.handleModBanReasonChange)}
703 {/* TODO hold off on expires until later */}
704 {/* <div class="form-group row"> */}
705 {/* <label class="col-form-label">Expires</label> */}
706 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
708 <div class="form-group row">
709 <button type="submit" class="btn btn-secondary">
710 {i18n.t('ban')} {node.comment.creator_name}
715 {this.state.showReply && (
718 onReplyCancel={this.handleReplyCancel}
719 disabled={this.props.locked}
723 {node.children && !this.state.collapsed && (
725 nodes={node.children}
726 locked={this.props.locked}
727 moderators={this.props.moderators}
728 admins={this.props.admins}
729 postCreatorId={this.props.postCreatorId}
730 sort={this.props.sort}
731 sortType={this.props.sortType}
732 enableDownvotes={this.props.enableDownvotes}
735 {/* A collapsed clearfix */}
736 {this.state.collapsed && <div class="row col-12"></div>}
742 let node = this.props.node;
745 class="btn btn-link btn-animate text-muted"
746 to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
747 title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')}
749 <svg class="icon icon-inline">
750 <use xlinkHref="#icon-link"></use>
758 <svg class="icon icon-spinner spin">
759 <use xlinkHref="#icon-spinner"></use>
764 get myComment(): boolean {
766 UserService.Instance.user &&
767 this.props.node.comment.creator_id == UserService.Instance.user.id
771 get isMod(): boolean {
773 this.props.moderators &&
775 this.props.moderators.map(m => m.user_id),
776 this.props.node.comment.creator_id
781 get isAdmin(): boolean {
785 this.props.admins.map(a => a.id),
786 this.props.node.comment.creator_id
791 get isPostCreator(): boolean {
792 return this.props.node.comment.creator_id == this.props.postCreatorId;
795 get canMod(): boolean {
796 if (this.props.admins && this.props.moderators) {
797 let adminsThenMods = this.props.admins
799 .concat(this.props.moderators.map(m => m.user_id));
802 UserService.Instance.user,
804 this.props.node.comment.creator_id
811 get canAdmin(): boolean {
815 UserService.Instance.user,
816 this.props.admins.map(a => a.id),
817 this.props.node.comment.creator_id
822 get amCommunityCreator(): boolean {
824 this.props.moderators &&
825 UserService.Instance.user &&
826 this.props.node.comment.creator_id != UserService.Instance.user.id &&
827 UserService.Instance.user.id == this.props.moderators[0].user_id
831 get amSiteCreator(): boolean {
834 UserService.Instance.user &&
835 this.props.node.comment.creator_id != UserService.Instance.user.id &&
836 UserService.Instance.user.id == this.props.admins[0].id
840 get commentUnlessRemoved(): string {
841 let node = this.props.node;
842 return node.comment.removed
843 ? `*${i18n.t('removed')}*`
844 : node.comment.deleted
845 ? `*${i18n.t('deleted')}*`
846 : node.comment.content;
849 handleReplyClick(i: CommentNode) {
850 i.state.showReply = true;
854 handleEditClick(i: CommentNode) {
855 i.state.showEdit = true;
859 handleDeleteClick(i: CommentNode) {
860 let deleteForm: DeleteCommentForm = {
861 edit_id: i.props.node.comment.id,
862 deleted: !i.props.node.comment.deleted,
865 WebSocketService.Instance.deleteComment(deleteForm);
868 handleSaveCommentClick(i: CommentNode) {
870 i.props.node.comment.saved == undefined
872 : !i.props.node.comment.saved;
873 let form: SaveCommentForm = {
874 comment_id: i.props.node.comment.id,
878 WebSocketService.Instance.saveComment(form);
880 i.state.saveLoading = true;
881 i.setState(this.state);
884 handleReplyCancel() {
885 this.state.showReply = false;
886 this.state.showEdit = false;
887 this.setState(this.state);
890 handleCommentUpvote(i: CommentNodeI) {
891 let new_vote = this.state.my_vote == 1 ? 0 : 1;
893 if (this.state.my_vote == 1) {
895 this.state.upvotes--;
896 } else if (this.state.my_vote == -1) {
897 this.state.downvotes--;
898 this.state.upvotes++;
899 this.state.score += 2;
901 this.state.upvotes++;
905 this.state.my_vote = new_vote;
907 let form: CommentLikeForm = {
908 comment_id: i.comment.id,
909 score: this.state.my_vote,
912 WebSocketService.Instance.likeComment(form);
913 this.setState(this.state);
917 handleCommentDownvote(i: CommentNodeI) {
918 let new_vote = this.state.my_vote == -1 ? 0 : -1;
920 if (this.state.my_vote == 1) {
921 this.state.score -= 2;
922 this.state.upvotes--;
923 this.state.downvotes++;
924 } else if (this.state.my_vote == -1) {
925 this.state.downvotes--;
928 this.state.downvotes++;
932 this.state.my_vote = new_vote;
934 let form: CommentLikeForm = {
935 comment_id: i.comment.id,
936 score: this.state.my_vote,
939 WebSocketService.Instance.likeComment(form);
940 this.setState(this.state);
944 handleModRemoveShow(i: CommentNode) {
945 i.state.showRemoveDialog = true;
949 handleModRemoveReasonChange(i: CommentNode, event: any) {
950 i.state.removeReason = event.target.value;
954 handleModRemoveSubmit(i: CommentNode) {
955 event.preventDefault();
956 let form: RemoveCommentForm = {
957 edit_id: i.props.node.comment.id,
958 removed: !i.props.node.comment.removed,
959 reason: i.state.removeReason,
962 WebSocketService.Instance.removeComment(form);
964 i.state.showRemoveDialog = false;
968 handleMarkRead(i: CommentNode) {
969 // if it has a user_mention_id field, then its a mention
970 if (i.props.node.comment.user_mention_id) {
971 let form: MarkUserMentionAsReadForm = {
972 user_mention_id: i.props.node.comment.user_mention_id,
973 read: !i.props.node.comment.read,
975 WebSocketService.Instance.markUserMentionAsRead(form);
977 let form: MarkCommentAsReadForm = {
978 edit_id: i.props.node.comment.id,
979 read: !i.props.node.comment.read,
982 WebSocketService.Instance.markCommentAsRead(form);
985 i.state.readLoading = true;
986 i.setState(this.state);
989 handleModBanFromCommunityShow(i: CommentNode) {
990 i.state.showBanDialog = !i.state.showBanDialog;
991 i.state.banType = BanType.Community;
995 handleModBanShow(i: CommentNode) {
996 i.state.showBanDialog = !i.state.showBanDialog;
997 i.state.banType = BanType.Site;
1001 handleModBanReasonChange(i: CommentNode, event: any) {
1002 i.state.banReason = event.target.value;
1003 i.setState(i.state);
1006 handleModBanExpiresChange(i: CommentNode, event: any) {
1007 i.state.banExpires = event.target.value;
1008 i.setState(i.state);
1011 handleModBanFromCommunitySubmit(i: CommentNode) {
1012 i.state.banType = BanType.Community;
1013 i.setState(i.state);
1014 i.handleModBanBothSubmit(i);
1017 handleModBanSubmit(i: CommentNode) {
1018 i.state.banType = BanType.Site;
1019 i.setState(i.state);
1020 i.handleModBanBothSubmit(i);
1023 handleModBanBothSubmit(i: CommentNode) {
1024 event.preventDefault();
1026 if (i.state.banType == BanType.Community) {
1027 let form: BanFromCommunityForm = {
1028 user_id: i.props.node.comment.creator_id,
1029 community_id: i.props.node.comment.community_id,
1030 ban: !i.props.node.comment.banned_from_community,
1031 reason: i.state.banReason,
1032 expires: getUnixTime(i.state.banExpires),
1034 WebSocketService.Instance.banFromCommunity(form);
1036 let form: BanUserForm = {
1037 user_id: i.props.node.comment.creator_id,
1038 ban: !i.props.node.comment.banned,
1039 reason: i.state.banReason,
1040 expires: getUnixTime(i.state.banExpires),
1042 WebSocketService.Instance.banUser(form);
1045 i.state.showBanDialog = false;
1046 i.setState(i.state);
1049 handleShowConfirmAppointAsMod(i: CommentNode) {
1050 i.state.showConfirmAppointAsMod = true;
1051 i.setState(i.state);
1054 handleCancelConfirmAppointAsMod(i: CommentNode) {
1055 i.state.showConfirmAppointAsMod = false;
1056 i.setState(i.state);
1059 handleAddModToCommunity(i: CommentNode) {
1060 let form: AddModToCommunityForm = {
1061 user_id: i.props.node.comment.creator_id,
1062 community_id: i.props.node.comment.community_id,
1065 WebSocketService.Instance.addModToCommunity(form);
1066 i.state.showConfirmAppointAsMod = false;
1067 i.setState(i.state);
1070 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1071 i.state.showConfirmAppointAsAdmin = true;
1072 i.setState(i.state);
1075 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1076 i.state.showConfirmAppointAsAdmin = false;
1077 i.setState(i.state);
1080 handleAddAdmin(i: CommentNode) {
1081 let form: AddAdminForm = {
1082 user_id: i.props.node.comment.creator_id,
1085 WebSocketService.Instance.addAdmin(form);
1086 i.state.showConfirmAppointAsAdmin = false;
1087 i.setState(i.state);
1090 handleShowConfirmTransferCommunity(i: CommentNode) {
1091 i.state.showConfirmTransferCommunity = true;
1092 i.setState(i.state);
1095 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1096 i.state.showConfirmTransferCommunity = false;
1097 i.setState(i.state);
1100 handleTransferCommunity(i: CommentNode) {
1101 let form: TransferCommunityForm = {
1102 community_id: i.props.node.comment.community_id,
1103 user_id: i.props.node.comment.creator_id,
1105 WebSocketService.Instance.transferCommunity(form);
1106 i.state.showConfirmTransferCommunity = false;
1107 i.setState(i.state);
1110 handleShowConfirmTransferSite(i: CommentNode) {
1111 i.state.showConfirmTransferSite = true;
1112 i.setState(i.state);
1115 handleCancelShowConfirmTransferSite(i: CommentNode) {
1116 i.state.showConfirmTransferSite = false;
1117 i.setState(i.state);
1120 handleTransferSite(i: CommentNode) {
1121 let form: TransferSiteForm = {
1122 user_id: i.props.node.comment.creator_id,
1124 WebSocketService.Instance.transferSite(form);
1125 i.state.showConfirmTransferSite = false;
1126 i.setState(i.state);
1129 get isCommentNew(): boolean {
1130 let now = moment.utc().subtract(10, 'minutes');
1131 let then = moment.utc(this.props.node.comment.published);
1132 return now.isBefore(then);
1135 handleCommentCollapse(i: CommentNode) {
1136 i.state.collapsed = !i.state.collapsed;
1137 i.setState(i.state);
1140 handleViewSource(i: CommentNode) {
1141 i.state.viewSource = !i.state.viewSource;
1142 i.setState(i.state);
1145 handleShowAdvanced(i: CommentNode) {
1146 i.state.showAdvanced = !i.state.showAdvanced;
1147 i.setState(i.state);
1152 if (this.state.my_vote == 1) {
1154 } else if (this.state.my_vote == -1) {
1155 return 'text-danger';
1157 return 'text-muted';
1161 get pointsTippy(): string {
1162 let points = i18n.t('number_of_points', {
1163 count: this.state.score,
1166 let upvotes = i18n.t('number_of_upvotes', {
1167 count: this.state.upvotes,
1170 let downvotes = i18n.t('number_of_downvotes', {
1171 count: this.state.downvotes,
1174 return `${points} • ${upvotes} • ${downvotes}`;