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';
27 pictshareAvatarThumbnail,
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 { i18n } from '../i18next';
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 moderators: Array<CommunityUser>;
70 admins: Array<UserView>;
71 // TODO is this necessary, can't I get it from the node itself?
72 postCreatorId?: number;
73 showCommunity?: boolean;
74 sort?: CommentSortType;
78 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
79 private emptyState: CommentNodeState = {
82 showRemoveDialog: false,
87 banType: BanType.Community,
91 showConfirmTransferSite: false,
92 showConfirmTransferCommunity: false,
93 showConfirmAppointAsMod: false,
94 showConfirmAppointAsAdmin: false,
95 my_vote: this.props.node.comment.my_vote,
96 score: this.props.node.comment.score,
97 upvotes: this.props.node.comment.upvotes,
98 downvotes: this.props.node.comment.downvotes,
99 borderColor: this.props.node.comment.depth
100 ? colorList[this.props.node.comment.depth % colorList.length]
106 constructor(props: any, context: any) {
107 super(props, context);
109 this.state = this.emptyState;
110 this.handleReplyCancel = this.handleReplyCancel.bind(this);
111 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
112 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
115 componentWillReceiveProps(nextProps: CommentNodeProps) {
116 this.state.my_vote = nextProps.node.comment.my_vote;
117 this.state.upvotes = nextProps.node.comment.upvotes;
118 this.state.downvotes = nextProps.node.comment.downvotes;
119 this.state.score = nextProps.node.comment.score;
120 this.state.readLoading = false;
121 this.state.saveLoading = false;
122 this.setState(this.state);
126 let node = this.props.node;
129 className={`comment ${
130 node.comment.parent_id && !this.props.noIndent ? 'ml-1' : ''
134 id={`comment-${node.comment.id}`}
135 className={`details comment-node border-top border-light ${
136 this.isCommentNew ? 'mark' : ''
139 !this.props.noIndent &&
140 this.props.node.comment.parent_id &&
141 `border-left: 2px ${this.state.borderColor} solid !important`
145 class={`${!this.props.noIndent &&
146 this.props.node.comment.parent_id &&
149 <ul class="list-inline mb-1 text-muted small">
150 <li className="mt-1 list-inline-item">
152 className="text-body font-weight-bold"
153 to={`/u/${node.comment.creator_name}`}
155 {node.comment.creator_avatar && showAvatars() && (
159 src={pictshareAvatarThumbnail(
160 node.comment.creator_avatar
162 class="rounded-circle mr-1"
165 <span>{node.comment.creator_name}</span>
169 <li className="list-inline-item badge badge-light">
174 <li className="list-inline-item badge badge-light">
178 {this.isPostCreator && (
179 <li className="list-inline-item badge badge-light">
183 {(node.comment.banned_from_community || node.comment.banned) && (
184 <li className="list-inline-item badge badge-danger">
188 <li className="list-inline-item">
190 className="unselectable pointer text-monospace"
191 onClick={linkEvent(this, this.handleCommentCollapse)}
193 {this.state.collapsed ? (
194 <svg class="icon icon-inline">
195 <use xlinkHref="#icon-plus-square"></use>
198 <svg class="icon icon-inline">
199 <use xlinkHref="#icon-minus-square"></use>
204 {this.props.showCommunity && (
205 <li className="list-inline-item">
206 <span> {i18n.t('to')} </span>
207 <Link to={`/c/${node.comment.community_name}`}>
208 {node.comment.community_name}
212 <li className="list-inline-item">•</li>
213 <li className="list-inline-item">
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>
225 <li className="list-inline-item">•</li>
226 <li className="list-inline-item">
228 <MomentTime data={node.comment} />
232 {this.state.showEdit && (
236 onReplyCancel={this.handleReplyCancel}
237 disabled={this.props.locked}
240 {!this.state.showEdit && !this.state.collapsed && (
242 {this.state.viewSource ? (
243 <pre>{this.commentUnlessRemoved}</pre>
247 dangerouslySetInnerHTML={mdToHtml(
248 this.commentUnlessRemoved
252 <ul class="list-inline mb-0 text-muted font-weight-bold small">
253 {this.props.markable && (
254 <li className="list-inline-item">
256 class="btn btn-link btn-sm btn-animate text-muted"
257 onClick={linkEvent(this, this.handleMarkRead)}
260 ? i18n.t('mark_as_unread')
261 : i18n.t('mark_as_read')
264 {this.state.readLoading ? (
268 class={`icon icon-inline ${node.comment.read &&
271 <use xlinkHref="#icon-check"></use>
277 {UserService.Instance.user && !this.props.viewOnly && (
279 <li className="list-inline-item">
281 className={`btn btn-link btn-sm 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')}
287 <svg class="icon icon-inline">
288 <use xlinkHref="#icon-arrow-up"></use>
290 {this.state.upvotes !== this.state.score && (
291 <span class="ml-1">{this.state.upvotes}</span>
295 {WebSocketService.Instance.site.enable_downvotes && (
296 <li className="list-inline-item">
298 className={`btn btn-link btn-sm btn-animate ${
299 this.state.my_vote == -1
305 this.handleCommentDownvote
307 data-tippy-content={i18n.t('downvote')}
309 <svg class="icon icon-inline">
310 <use xlinkHref="#icon-arrow-down"></use>
312 {this.state.upvotes !== this.state.score && (
313 <span class="ml-1">{this.state.downvotes}</span>
318 <li className="list-inline-item">
320 class="btn btn-link btn-sm btn-animate text-muted"
321 onClick={linkEvent(this, this.handleSaveCommentClick)}
328 {this.state.saveLoading ? (
332 class={`icon icon-inline ${node.comment.saved &&
335 <use xlinkHref="#icon-star"></use>
340 <li className="list-inline-item">
342 class="btn btn-link btn-sm btn-animate text-muted"
343 onClick={linkEvent(this, this.handleReplyClick)}
344 data-tippy-content={i18n.t('reply')}
346 <svg class="icon icon-inline">
347 <use xlinkHref="#icon-reply1"></use>
351 {this.props.markable && this.linkBtn}
352 {!this.state.showAdvanced ? (
353 <li className="list-inline-item">
355 className="btn btn-link btn-sm btn-animate text-muted"
356 onClick={linkEvent(this, this.handleShowAdvanced)}
357 data-tippy-content={i18n.t('more')}
359 <svg class="icon icon-inline">
360 <use xlinkHref="#icon-more-vertical"></use>
366 {!this.myComment && (
367 <li className="list-inline-item">
369 class="btn btn-link btn-sm btn-animate text-muted"
370 to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
371 title={i18n.t('message').toLowerCase()}
374 <use xlinkHref="#icon-mail"></use>
379 {!this.props.markable && this.linkBtn}
380 <li className="list-inline-item">
382 className="btn btn-link btn-sm btn-animate text-muted"
383 onClick={linkEvent(this, this.handleViewSource)}
384 data-tippy-content={i18n.t('view_source')}
387 class={`icon icon-inline ${this.state
388 .viewSource && 'text-success'}`}
390 <use xlinkHref="#icon-file-text"></use>
396 <li className="list-inline-item">•</li>
397 <li className="list-inline-item">
399 class="btn btn-link btn-sm btn-animate text-muted"
404 data-tippy-content={i18n.t('edit')}
406 <svg class="icon icon-inline">
407 <use xlinkHref="#icon-edit"></use>
411 <li className="list-inline-item">
413 class="btn btn-link btn-sm btn-animate text-muted"
416 this.handleDeleteClick
419 !node.comment.deleted
425 class={`icon icon-inline ${node.comment
426 .deleted && 'text-danger'}`}
428 <use xlinkHref="#icon-trash"></use>
434 {/* Admins and mods can remove comments */}
435 {(this.canMod || this.canAdmin) && (
437 <li className="list-inline-item">
438 {!node.comment.removed ? (
443 this.handleModRemoveShow
453 this.handleModRemoveSubmit
462 {/* Mods can ban from community, and appoint as mods to community */}
466 <li className="list-inline-item">
467 {!node.comment.banned_from_community ? (
472 this.handleModBanFromCommunityShow
482 this.handleModBanFromCommunitySubmit
490 {!node.comment.banned_from_community && (
491 <li className="list-inline-item">
492 {!this.state.showConfirmAppointAsMod ? (
497 this.handleShowConfirmAppointAsMod
501 ? i18n.t('remove_as_mod')
502 : i18n.t('appoint_as_mod')}
506 <span class="d-inline-block mr-1">
507 {i18n.t('are_you_sure')}
510 class="pointer d-inline-block mr-1"
513 this.handleAddModToCommunity
519 class="pointer d-inline-block"
522 this.handleCancelConfirmAppointAsMod
533 {/* Community creators and admins can transfer community to another mod */}
534 {(this.amCommunityCreator || this.canAdmin) &&
536 <li className="list-inline-item">
537 {!this.state.showConfirmTransferCommunity ? (
542 this.handleShowConfirmTransferCommunity
545 {i18n.t('transfer_community')}
549 <span class="d-inline-block mr-1">
550 {i18n.t('are_you_sure')}
553 class="pointer d-inline-block mr-1"
556 this.handleTransferCommunity
562 class="pointer d-inline-block"
566 .handleCancelShowConfirmTransferCommunity
575 {/* Admins can ban from all, and appoint other admins */}
579 <li className="list-inline-item">
580 {!node.comment.banned ? (
585 this.handleModBanShow
588 {i18n.t('ban_from_site')}
595 this.handleModBanSubmit
598 {i18n.t('unban_from_site')}
603 {!node.comment.banned && (
604 <li className="list-inline-item">
605 {!this.state.showConfirmAppointAsAdmin ? (
610 this.handleShowConfirmAppointAsAdmin
614 ? i18n.t('remove_as_admin')
615 : i18n.t('appoint_as_admin')}
619 <span class="d-inline-block mr-1">
620 {i18n.t('are_you_sure')}
623 class="pointer d-inline-block mr-1"
632 class="pointer d-inline-block"
635 this.handleCancelConfirmAppointAsAdmin
646 {/* Site Creator can transfer to another admin */}
647 {this.amSiteCreator && this.isAdmin && (
648 <li className="list-inline-item">
649 {!this.state.showConfirmTransferSite ? (
654 this.handleShowConfirmTransferSite
657 {i18n.t('transfer_site')}
661 <span class="d-inline-block mr-1">
662 {i18n.t('are_you_sure')}
665 class="pointer d-inline-block mr-1"
668 this.handleTransferSite
674 class="pointer d-inline-block"
677 this.handleCancelShowConfirmTransferSite
695 {/* end of details */}
696 {this.state.showRemoveDialog && (
699 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
703 class="form-control mr-2"
704 placeholder={i18n.t('reason')}
705 value={this.state.removeReason}
706 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
708 <button type="submit" class="btn btn-secondary">
709 {i18n.t('remove_comment')}
713 {this.state.showBanDialog && (
714 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
715 <div class="form-group row">
716 <label class="col-form-label">{i18n.t('reason')}</label>
719 class="form-control mr-2"
720 placeholder={i18n.t('reason')}
721 value={this.state.banReason}
722 onInput={linkEvent(this, this.handleModBanReasonChange)}
725 {/* TODO hold off on expires until later */}
726 {/* <div class="form-group row"> */}
727 {/* <label class="col-form-label">Expires</label> */}
728 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
730 <div class="form-group row">
731 <button type="submit" class="btn btn-secondary">
732 {i18n.t('ban')} {node.comment.creator_name}
737 {this.state.showReply && (
740 onReplyCancel={this.handleReplyCancel}
741 disabled={this.props.locked}
744 {node.children && !this.state.collapsed && (
746 nodes={node.children}
747 locked={this.props.locked}
748 moderators={this.props.moderators}
749 admins={this.props.admins}
750 postCreatorId={this.props.postCreatorId}
751 sort={this.props.sort}
752 sortType={this.props.sortType}
755 {/* A collapsed clearfix */}
756 {this.state.collapsed && <div class="row col-12"></div>}
762 let node = this.props.node;
764 <li className="list-inline-item">
766 className="btn btn-link btn-sm btn-animate text-muted"
767 to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
768 title={i18n.t('link')}
770 <svg class="icon icon-inline">
771 <use xlinkHref="#icon-link"></use>
780 <svg class="icon icon-spinner spin">
781 <use xlinkHref="#icon-spinner"></use>
786 get myComment(): boolean {
788 UserService.Instance.user &&
789 this.props.node.comment.creator_id == UserService.Instance.user.id
793 get isMod(): boolean {
795 this.props.moderators &&
797 this.props.moderators.map(m => m.user_id),
798 this.props.node.comment.creator_id
803 get isAdmin(): boolean {
807 this.props.admins.map(a => a.id),
808 this.props.node.comment.creator_id
813 get isPostCreator(): boolean {
814 return this.props.node.comment.creator_id == this.props.postCreatorId;
817 get canMod(): boolean {
818 if (this.props.admins && this.props.moderators) {
819 let adminsThenMods = this.props.admins
821 .concat(this.props.moderators.map(m => m.user_id));
824 UserService.Instance.user,
826 this.props.node.comment.creator_id
833 get canAdmin(): boolean {
837 UserService.Instance.user,
838 this.props.admins.map(a => a.id),
839 this.props.node.comment.creator_id
844 get amCommunityCreator(): boolean {
846 this.props.moderators &&
847 UserService.Instance.user &&
848 this.props.node.comment.creator_id != UserService.Instance.user.id &&
849 UserService.Instance.user.id == this.props.moderators[0].user_id
853 get amSiteCreator(): boolean {
856 UserService.Instance.user &&
857 this.props.node.comment.creator_id != UserService.Instance.user.id &&
858 UserService.Instance.user.id == this.props.admins[0].id
862 get commentUnlessRemoved(): string {
863 let node = this.props.node;
864 return node.comment.removed
865 ? `*${i18n.t('removed')}*`
866 : node.comment.deleted
867 ? `*${i18n.t('deleted')}*`
868 : node.comment.content;
871 handleReplyClick(i: CommentNode) {
872 i.state.showReply = true;
876 handleEditClick(i: CommentNode) {
877 i.state.showEdit = true;
881 handleDeleteClick(i: CommentNode) {
882 let deleteForm: CommentFormI = {
883 content: i.props.node.comment.content,
884 edit_id: i.props.node.comment.id,
885 creator_id: i.props.node.comment.creator_id,
886 post_id: i.props.node.comment.post_id,
887 parent_id: i.props.node.comment.parent_id,
888 deleted: !i.props.node.comment.deleted,
891 WebSocketService.Instance.editComment(deleteForm);
894 handleSaveCommentClick(i: CommentNode) {
896 i.props.node.comment.saved == undefined
898 : !i.props.node.comment.saved;
899 let form: SaveCommentForm = {
900 comment_id: i.props.node.comment.id,
904 WebSocketService.Instance.saveComment(form);
906 i.state.saveLoading = true;
907 i.setState(this.state);
910 handleReplyCancel() {
911 this.state.showReply = false;
912 this.state.showEdit = false;
913 this.setState(this.state);
916 handleCommentUpvote(i: CommentNodeI) {
917 let new_vote = this.state.my_vote == 1 ? 0 : 1;
919 if (this.state.my_vote == 1) {
921 this.state.upvotes--;
922 } else if (this.state.my_vote == -1) {
923 this.state.downvotes--;
924 this.state.upvotes++;
925 this.state.score += 2;
927 this.state.upvotes++;
931 this.state.my_vote = new_vote;
933 let form: CommentLikeForm = {
934 comment_id: i.comment.id,
935 post_id: i.comment.post_id,
936 score: this.state.my_vote,
939 WebSocketService.Instance.likeComment(form);
940 this.setState(this.state);
944 handleCommentDownvote(i: CommentNodeI) {
945 let new_vote = this.state.my_vote == -1 ? 0 : -1;
947 if (this.state.my_vote == 1) {
948 this.state.score -= 2;
949 this.state.upvotes--;
950 this.state.downvotes++;
951 } else if (this.state.my_vote == -1) {
952 this.state.downvotes--;
955 this.state.downvotes++;
959 this.state.my_vote = new_vote;
961 let form: CommentLikeForm = {
962 comment_id: i.comment.id,
963 post_id: i.comment.post_id,
964 score: this.state.my_vote,
967 WebSocketService.Instance.likeComment(form);
968 this.setState(this.state);
972 handleModRemoveShow(i: CommentNode) {
973 i.state.showRemoveDialog = true;
977 handleModRemoveReasonChange(i: CommentNode, event: any) {
978 i.state.removeReason = event.target.value;
982 handleModRemoveSubmit(i: CommentNode) {
983 event.preventDefault();
984 let form: CommentFormI = {
985 content: i.props.node.comment.content,
986 edit_id: i.props.node.comment.id,
987 creator_id: i.props.node.comment.creator_id,
988 post_id: i.props.node.comment.post_id,
989 parent_id: i.props.node.comment.parent_id,
990 removed: !i.props.node.comment.removed,
991 reason: i.state.removeReason,
994 WebSocketService.Instance.editComment(form);
996 i.state.showRemoveDialog = false;
1000 handleMarkRead(i: CommentNode) {
1001 // if it has a user_mention_id field, then its a mention
1002 if (i.props.node.comment.user_mention_id) {
1003 let form: EditUserMentionForm = {
1004 user_mention_id: i.props.node.comment.user_mention_id,
1005 read: !i.props.node.comment.read,
1007 WebSocketService.Instance.editUserMention(form);
1009 let form: CommentFormI = {
1010 content: i.props.node.comment.content,
1011 edit_id: i.props.node.comment.id,
1012 creator_id: i.props.node.comment.creator_id,
1013 post_id: i.props.node.comment.post_id,
1014 parent_id: i.props.node.comment.parent_id,
1015 read: !i.props.node.comment.read,
1018 WebSocketService.Instance.editComment(form);
1021 i.state.readLoading = true;
1022 i.setState(this.state);
1025 handleModBanFromCommunityShow(i: CommentNode) {
1026 i.state.showBanDialog = !i.state.showBanDialog;
1027 i.state.banType = BanType.Community;
1028 i.setState(i.state);
1031 handleModBanShow(i: CommentNode) {
1032 i.state.showBanDialog = !i.state.showBanDialog;
1033 i.state.banType = BanType.Site;
1034 i.setState(i.state);
1037 handleModBanReasonChange(i: CommentNode, event: any) {
1038 i.state.banReason = event.target.value;
1039 i.setState(i.state);
1042 handleModBanExpiresChange(i: CommentNode, event: any) {
1043 i.state.banExpires = event.target.value;
1044 i.setState(i.state);
1047 handleModBanFromCommunitySubmit(i: CommentNode) {
1048 i.state.banType = BanType.Community;
1049 i.setState(i.state);
1050 i.handleModBanBothSubmit(i);
1053 handleModBanSubmit(i: CommentNode) {
1054 i.state.banType = BanType.Site;
1055 i.setState(i.state);
1056 i.handleModBanBothSubmit(i);
1059 handleModBanBothSubmit(i: CommentNode) {
1060 event.preventDefault();
1062 if (i.state.banType == BanType.Community) {
1063 let form: BanFromCommunityForm = {
1064 user_id: i.props.node.comment.creator_id,
1065 community_id: i.props.node.comment.community_id,
1066 ban: !i.props.node.comment.banned_from_community,
1067 reason: i.state.banReason,
1068 expires: getUnixTime(i.state.banExpires),
1070 WebSocketService.Instance.banFromCommunity(form);
1072 let form: BanUserForm = {
1073 user_id: i.props.node.comment.creator_id,
1074 ban: !i.props.node.comment.banned,
1075 reason: i.state.banReason,
1076 expires: getUnixTime(i.state.banExpires),
1078 WebSocketService.Instance.banUser(form);
1081 i.state.showBanDialog = false;
1082 i.setState(i.state);
1085 handleShowConfirmAppointAsMod(i: CommentNode) {
1086 i.state.showConfirmAppointAsMod = true;
1087 i.setState(i.state);
1090 handleCancelConfirmAppointAsMod(i: CommentNode) {
1091 i.state.showConfirmAppointAsMod = false;
1092 i.setState(i.state);
1095 handleAddModToCommunity(i: CommentNode) {
1096 let form: AddModToCommunityForm = {
1097 user_id: i.props.node.comment.creator_id,
1098 community_id: i.props.node.comment.community_id,
1101 WebSocketService.Instance.addModToCommunity(form);
1102 i.state.showConfirmAppointAsMod = false;
1103 i.setState(i.state);
1106 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1107 i.state.showConfirmAppointAsAdmin = true;
1108 i.setState(i.state);
1111 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1112 i.state.showConfirmAppointAsAdmin = false;
1113 i.setState(i.state);
1116 handleAddAdmin(i: CommentNode) {
1117 let form: AddAdminForm = {
1118 user_id: i.props.node.comment.creator_id,
1121 WebSocketService.Instance.addAdmin(form);
1122 i.state.showConfirmAppointAsAdmin = false;
1123 i.setState(i.state);
1126 handleShowConfirmTransferCommunity(i: CommentNode) {
1127 i.state.showConfirmTransferCommunity = true;
1128 i.setState(i.state);
1131 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1132 i.state.showConfirmTransferCommunity = false;
1133 i.setState(i.state);
1136 handleTransferCommunity(i: CommentNode) {
1137 let form: TransferCommunityForm = {
1138 community_id: i.props.node.comment.community_id,
1139 user_id: i.props.node.comment.creator_id,
1141 WebSocketService.Instance.transferCommunity(form);
1142 i.state.showConfirmTransferCommunity = false;
1143 i.setState(i.state);
1146 handleShowConfirmTransferSite(i: CommentNode) {
1147 i.state.showConfirmTransferSite = true;
1148 i.setState(i.state);
1151 handleCancelShowConfirmTransferSite(i: CommentNode) {
1152 i.state.showConfirmTransferSite = false;
1153 i.setState(i.state);
1156 handleTransferSite(i: CommentNode) {
1157 let form: TransferSiteForm = {
1158 user_id: i.props.node.comment.creator_id,
1160 WebSocketService.Instance.transferSite(form);
1161 i.state.showConfirmTransferSite = false;
1162 i.setState(i.state);
1165 get isCommentNew(): boolean {
1166 let now = moment.utc().subtract(10, 'minutes');
1167 let then = moment.utc(this.props.node.comment.published);
1168 return now.isBefore(then);
1171 handleCommentCollapse(i: CommentNode) {
1172 i.state.collapsed = !i.state.collapsed;
1173 i.setState(i.state);
1176 handleViewSource(i: CommentNode) {
1177 i.state.viewSource = !i.state.viewSource;
1178 i.setState(i.state);
1181 handleShowAdvanced(i: CommentNode) {
1182 i.state.showAdvanced = !i.state.showAdvanced;
1183 i.setState(i.state);
1188 if (this.state.my_vote == 1) {
1190 } else if (this.state.my_vote == -1) {
1191 return 'text-danger';
1193 return 'text-muted';
1197 get pointsTippy(): string {
1198 let points = i18n.t('number_of_points', {
1199 count: this.state.score,
1202 let upvotes = i18n.t('number_of_upvotes', {
1203 count: this.state.upvotes,
1206 let downvotes = i18n.t('number_of_downvotes', {
1207 count: this.state.downvotes,
1210 return `${points} • ${upvotes} • ${downvotes}`;