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 d-none d-sm-inline">
174 <li className="list-inline-item badge badge-light d-none d-sm-inline">
178 {this.isPostCreator && (
179 <li className="list-inline-item badge badge-light d-none d-sm-inline">
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="ml-3 list-inline-item">
214 className={`unselectable pointer ${this.scoreColor}`}
215 onClick={linkEvent(node, this.handleCommentUpvote)}
216 data-tippy-content={this.pointsTippy}
218 <svg class="icon icon-inline mr-1">
219 <use xlinkHref="#icon-zap"></use>
224 <li className="list-inline-item">•</li>
225 <li className="list-inline-item">
227 <MomentTime data={node.comment} />
231 {this.state.showEdit && (
235 onReplyCancel={this.handleReplyCancel}
236 disabled={this.props.locked}
239 {!this.state.showEdit && !this.state.collapsed && (
241 {this.state.viewSource ? (
242 <pre>{this.commentUnlessRemoved}</pre>
246 dangerouslySetInnerHTML={mdToHtml(
247 this.commentUnlessRemoved
251 <ul class="list-inline mb-0 text-muted font-weight-bold small">
252 {this.props.markable && (
253 <li className="list-inline-item">
255 class="btn btn-link btn-sm btn-animate text-muted"
256 onClick={linkEvent(this, this.handleMarkRead)}
259 ? i18n.t('mark_as_unread')
260 : i18n.t('mark_as_read')
263 {this.state.readLoading ? (
267 class={`icon icon-inline ${node.comment.read &&
270 <use xlinkHref="#icon-check"></use>
276 {UserService.Instance.user && !this.props.viewOnly && (
278 <li className="list-inline-item">
280 className={`btn btn-link btn-sm btn-animate ${
281 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
283 onClick={linkEvent(node, this.handleCommentUpvote)}
284 data-tippy-content={i18n.t('upvote')}
286 <svg class="icon icon-inline">
287 <use xlinkHref="#icon-arrow-up"></use>
289 {this.state.upvotes !== this.state.score && (
290 <span class="ml-1">{this.state.upvotes}</span>
294 {WebSocketService.Instance.site.enable_downvotes && (
295 <li className="list-inline-item">
297 className={`btn btn-link btn-sm btn-animate ${
298 this.state.my_vote == -1
304 this.handleCommentDownvote
306 data-tippy-content={i18n.t('downvote')}
308 <svg class="icon icon-inline">
309 <use xlinkHref="#icon-arrow-down"></use>
311 {this.state.upvotes !== this.state.score && (
312 <span class="ml-1">{this.state.downvotes}</span>
317 <li className="list-inline-item">
319 class="btn btn-link btn-sm btn-animate text-muted"
320 onClick={linkEvent(this, this.handleSaveCommentClick)}
327 {this.state.saveLoading ? (
331 class={`icon icon-inline ${node.comment.saved &&
334 <use xlinkHref="#icon-star"></use>
339 <li className="list-inline-item">
341 class="btn btn-link btn-sm btn-animate text-muted"
342 onClick={linkEvent(this, this.handleReplyClick)}
343 data-tippy-content={i18n.t('reply')}
345 <svg class="icon icon-inline">
346 <use xlinkHref="#icon-reply1"></use>
350 {this.props.markable && this.linkBtn}
351 {!this.state.showAdvanced ? (
352 <li className="list-inline-item">
354 className="btn btn-link btn-sm btn-animate text-muted"
355 onClick={linkEvent(this, this.handleShowAdvanced)}
356 data-tippy-content={i18n.t('more')}
358 <svg class="icon icon-inline">
359 <use xlinkHref="#icon-more-vertical"></use>
365 {!this.myComment && (
366 <li className="list-inline-item">
368 class="btn btn-link btn-sm btn-animate text-muted"
369 to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
370 title={i18n.t('message').toLowerCase()}
373 <use xlinkHref="#icon-mail"></use>
378 {!this.props.markable && this.linkBtn}
379 <li className="list-inline-item">
381 className="btn btn-link btn-sm btn-animate text-muted"
382 onClick={linkEvent(this, this.handleViewSource)}
383 data-tippy-content={i18n.t('view_source')}
386 class={`icon icon-inline ${this.state
387 .viewSource && 'text-success'}`}
389 <use xlinkHref="#icon-file-text"></use>
395 <li className="list-inline-item">•</li>
396 <li className="list-inline-item">
398 class="btn btn-link btn-sm btn-animate text-muted"
403 data-tippy-content={i18n.t('edit')}
405 <svg class="icon icon-inline">
406 <use xlinkHref="#icon-edit"></use>
410 <li className="list-inline-item">
412 class="btn btn-link btn-sm btn-animate text-muted"
415 this.handleDeleteClick
418 !node.comment.deleted
424 class={`icon icon-inline ${node.comment
425 .deleted && 'text-danger'}`}
427 <use xlinkHref="#icon-trash"></use>
433 {/* Admins and mods can remove comments */}
434 {(this.canMod || this.canAdmin) && (
436 <li className="list-inline-item">
437 {!node.comment.removed ? (
442 this.handleModRemoveShow
452 this.handleModRemoveSubmit
461 {/* Mods can ban from community, and appoint as mods to community */}
465 <li className="list-inline-item">
466 {!node.comment.banned_from_community ? (
471 this.handleModBanFromCommunityShow
481 this.handleModBanFromCommunitySubmit
489 {!node.comment.banned_from_community && (
490 <li className="list-inline-item">
491 {!this.state.showConfirmAppointAsMod ? (
496 this.handleShowConfirmAppointAsMod
500 ? i18n.t('remove_as_mod')
501 : i18n.t('appoint_as_mod')}
505 <span class="d-inline-block mr-1">
506 {i18n.t('are_you_sure')}
509 class="pointer d-inline-block mr-1"
512 this.handleAddModToCommunity
518 class="pointer d-inline-block"
521 this.handleCancelConfirmAppointAsMod
532 {/* Community creators and admins can transfer community to another mod */}
533 {(this.amCommunityCreator || this.canAdmin) &&
535 <li className="list-inline-item">
536 {!this.state.showConfirmTransferCommunity ? (
541 this.handleShowConfirmTransferCommunity
544 {i18n.t('transfer_community')}
548 <span class="d-inline-block mr-1">
549 {i18n.t('are_you_sure')}
552 class="pointer d-inline-block mr-1"
555 this.handleTransferCommunity
561 class="pointer d-inline-block"
565 .handleCancelShowConfirmTransferCommunity
574 {/* Admins can ban from all, and appoint other admins */}
578 <li className="list-inline-item">
579 {!node.comment.banned ? (
584 this.handleModBanShow
587 {i18n.t('ban_from_site')}
594 this.handleModBanSubmit
597 {i18n.t('unban_from_site')}
602 {!node.comment.banned && (
603 <li className="list-inline-item">
604 {!this.state.showConfirmAppointAsAdmin ? (
609 this.handleShowConfirmAppointAsAdmin
613 ? i18n.t('remove_as_admin')
614 : i18n.t('appoint_as_admin')}
618 <span class="d-inline-block mr-1">
619 {i18n.t('are_you_sure')}
622 class="pointer d-inline-block mr-1"
631 class="pointer d-inline-block"
634 this.handleCancelConfirmAppointAsAdmin
645 {/* Site Creator can transfer to another admin */}
646 {this.amSiteCreator && this.isAdmin && (
647 <li className="list-inline-item">
648 {!this.state.showConfirmTransferSite ? (
653 this.handleShowConfirmTransferSite
656 {i18n.t('transfer_site')}
660 <span class="d-inline-block mr-1">
661 {i18n.t('are_you_sure')}
664 class="pointer d-inline-block mr-1"
667 this.handleTransferSite
673 class="pointer d-inline-block"
676 this.handleCancelShowConfirmTransferSite
694 {/* end of details */}
695 {this.state.showRemoveDialog && (
698 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
702 class="form-control mr-2"
703 placeholder={i18n.t('reason')}
704 value={this.state.removeReason}
705 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
707 <button type="submit" class="btn btn-secondary">
708 {i18n.t('remove_comment')}
712 {this.state.showBanDialog && (
713 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
714 <div class="form-group row">
715 <label class="col-form-label">{i18n.t('reason')}</label>
718 class="form-control mr-2"
719 placeholder={i18n.t('reason')}
720 value={this.state.banReason}
721 onInput={linkEvent(this, this.handleModBanReasonChange)}
724 {/* TODO hold off on expires until later */}
725 {/* <div class="form-group row"> */}
726 {/* <label class="col-form-label">Expires</label> */}
727 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
729 <div class="form-group row">
730 <button type="submit" class="btn btn-secondary">
731 {i18n.t('ban')} {node.comment.creator_name}
736 {this.state.showReply && (
739 onReplyCancel={this.handleReplyCancel}
740 disabled={this.props.locked}
743 {node.children && !this.state.collapsed && (
745 nodes={node.children}
746 locked={this.props.locked}
747 moderators={this.props.moderators}
748 admins={this.props.admins}
749 postCreatorId={this.props.postCreatorId}
750 sort={this.props.sort}
751 sortType={this.props.sortType}
754 {/* A collapsed clearfix */}
755 {this.state.collapsed && <div class="row col-12"></div>}
761 let node = this.props.node;
763 <li className="list-inline-item">
765 className="btn btn-link btn-sm btn-animate text-muted"
766 to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
767 title={i18n.t('link')}
769 <svg class="icon icon-inline">
770 <use xlinkHref="#icon-link"></use>
779 <svg class="icon icon-spinner spin">
780 <use xlinkHref="#icon-spinner"></use>
785 get myComment(): boolean {
787 UserService.Instance.user &&
788 this.props.node.comment.creator_id == UserService.Instance.user.id
792 get isMod(): boolean {
794 this.props.moderators &&
796 this.props.moderators.map(m => m.user_id),
797 this.props.node.comment.creator_id
802 get isAdmin(): boolean {
806 this.props.admins.map(a => a.id),
807 this.props.node.comment.creator_id
812 get isPostCreator(): boolean {
813 return this.props.node.comment.creator_id == this.props.postCreatorId;
816 get canMod(): boolean {
817 if (this.props.admins && this.props.moderators) {
818 let adminsThenMods = this.props.admins
820 .concat(this.props.moderators.map(m => m.user_id));
823 UserService.Instance.user,
825 this.props.node.comment.creator_id
832 get canAdmin(): boolean {
836 UserService.Instance.user,
837 this.props.admins.map(a => a.id),
838 this.props.node.comment.creator_id
843 get amCommunityCreator(): boolean {
845 this.props.moderators &&
846 UserService.Instance.user &&
847 this.props.node.comment.creator_id != UserService.Instance.user.id &&
848 UserService.Instance.user.id == this.props.moderators[0].user_id
852 get amSiteCreator(): boolean {
855 UserService.Instance.user &&
856 this.props.node.comment.creator_id != UserService.Instance.user.id &&
857 UserService.Instance.user.id == this.props.admins[0].id
861 get commentUnlessRemoved(): string {
862 let node = this.props.node;
863 return node.comment.removed
864 ? `*${i18n.t('removed')}*`
865 : node.comment.deleted
866 ? `*${i18n.t('deleted')}*`
867 : node.comment.content;
870 handleReplyClick(i: CommentNode) {
871 i.state.showReply = true;
875 handleEditClick(i: CommentNode) {
876 i.state.showEdit = true;
880 handleDeleteClick(i: CommentNode) {
881 let deleteForm: CommentFormI = {
882 content: i.props.node.comment.content,
883 edit_id: i.props.node.comment.id,
884 creator_id: i.props.node.comment.creator_id,
885 post_id: i.props.node.comment.post_id,
886 parent_id: i.props.node.comment.parent_id,
887 deleted: !i.props.node.comment.deleted,
890 WebSocketService.Instance.editComment(deleteForm);
893 handleSaveCommentClick(i: CommentNode) {
895 i.props.node.comment.saved == undefined
897 : !i.props.node.comment.saved;
898 let form: SaveCommentForm = {
899 comment_id: i.props.node.comment.id,
903 WebSocketService.Instance.saveComment(form);
905 i.state.saveLoading = true;
906 i.setState(this.state);
909 handleReplyCancel() {
910 this.state.showReply = false;
911 this.state.showEdit = false;
912 this.setState(this.state);
915 handleCommentUpvote(i: CommentNodeI) {
916 let new_vote = this.state.my_vote == 1 ? 0 : 1;
918 if (this.state.my_vote == 1) {
920 this.state.upvotes--;
921 } else if (this.state.my_vote == -1) {
922 this.state.downvotes--;
923 this.state.upvotes++;
924 this.state.score += 2;
926 this.state.upvotes++;
930 this.state.my_vote = new_vote;
932 let form: CommentLikeForm = {
933 comment_id: i.comment.id,
934 post_id: i.comment.post_id,
935 score: this.state.my_vote,
938 WebSocketService.Instance.likeComment(form);
939 this.setState(this.state);
943 handleCommentDownvote(i: CommentNodeI) {
944 let new_vote = this.state.my_vote == -1 ? 0 : -1;
946 if (this.state.my_vote == 1) {
947 this.state.score -= 2;
948 this.state.upvotes--;
949 this.state.downvotes++;
950 } else if (this.state.my_vote == -1) {
951 this.state.downvotes--;
954 this.state.downvotes++;
958 this.state.my_vote = new_vote;
960 let form: CommentLikeForm = {
961 comment_id: i.comment.id,
962 post_id: i.comment.post_id,
963 score: this.state.my_vote,
966 WebSocketService.Instance.likeComment(form);
967 this.setState(this.state);
971 handleModRemoveShow(i: CommentNode) {
972 i.state.showRemoveDialog = true;
976 handleModRemoveReasonChange(i: CommentNode, event: any) {
977 i.state.removeReason = event.target.value;
981 handleModRemoveSubmit(i: CommentNode) {
982 event.preventDefault();
983 let form: CommentFormI = {
984 content: i.props.node.comment.content,
985 edit_id: i.props.node.comment.id,
986 creator_id: i.props.node.comment.creator_id,
987 post_id: i.props.node.comment.post_id,
988 parent_id: i.props.node.comment.parent_id,
989 removed: !i.props.node.comment.removed,
990 reason: i.state.removeReason,
993 WebSocketService.Instance.editComment(form);
995 i.state.showRemoveDialog = false;
999 handleMarkRead(i: CommentNode) {
1000 // if it has a user_mention_id field, then its a mention
1001 if (i.props.node.comment.user_mention_id) {
1002 let form: EditUserMentionForm = {
1003 user_mention_id: i.props.node.comment.user_mention_id,
1004 read: !i.props.node.comment.read,
1006 WebSocketService.Instance.editUserMention(form);
1008 let form: CommentFormI = {
1009 content: i.props.node.comment.content,
1010 edit_id: i.props.node.comment.id,
1011 creator_id: i.props.node.comment.creator_id,
1012 post_id: i.props.node.comment.post_id,
1013 parent_id: i.props.node.comment.parent_id,
1014 read: !i.props.node.comment.read,
1017 WebSocketService.Instance.editComment(form);
1020 i.state.readLoading = true;
1021 i.setState(this.state);
1024 handleModBanFromCommunityShow(i: CommentNode) {
1025 i.state.showBanDialog = !i.state.showBanDialog;
1026 i.state.banType = BanType.Community;
1027 i.setState(i.state);
1030 handleModBanShow(i: CommentNode) {
1031 i.state.showBanDialog = !i.state.showBanDialog;
1032 i.state.banType = BanType.Site;
1033 i.setState(i.state);
1036 handleModBanReasonChange(i: CommentNode, event: any) {
1037 i.state.banReason = event.target.value;
1038 i.setState(i.state);
1041 handleModBanExpiresChange(i: CommentNode, event: any) {
1042 i.state.banExpires = event.target.value;
1043 i.setState(i.state);
1046 handleModBanFromCommunitySubmit(i: CommentNode) {
1047 i.state.banType = BanType.Community;
1048 i.setState(i.state);
1049 i.handleModBanBothSubmit(i);
1052 handleModBanSubmit(i: CommentNode) {
1053 i.state.banType = BanType.Site;
1054 i.setState(i.state);
1055 i.handleModBanBothSubmit(i);
1058 handleModBanBothSubmit(i: CommentNode) {
1059 event.preventDefault();
1061 if (i.state.banType == BanType.Community) {
1062 let form: BanFromCommunityForm = {
1063 user_id: i.props.node.comment.creator_id,
1064 community_id: i.props.node.comment.community_id,
1065 ban: !i.props.node.comment.banned_from_community,
1066 reason: i.state.banReason,
1067 expires: getUnixTime(i.state.banExpires),
1069 WebSocketService.Instance.banFromCommunity(form);
1071 let form: BanUserForm = {
1072 user_id: i.props.node.comment.creator_id,
1073 ban: !i.props.node.comment.banned,
1074 reason: i.state.banReason,
1075 expires: getUnixTime(i.state.banExpires),
1077 WebSocketService.Instance.banUser(form);
1080 i.state.showBanDialog = false;
1081 i.setState(i.state);
1084 handleShowConfirmAppointAsMod(i: CommentNode) {
1085 i.state.showConfirmAppointAsMod = true;
1086 i.setState(i.state);
1089 handleCancelConfirmAppointAsMod(i: CommentNode) {
1090 i.state.showConfirmAppointAsMod = false;
1091 i.setState(i.state);
1094 handleAddModToCommunity(i: CommentNode) {
1095 let form: AddModToCommunityForm = {
1096 user_id: i.props.node.comment.creator_id,
1097 community_id: i.props.node.comment.community_id,
1100 WebSocketService.Instance.addModToCommunity(form);
1101 i.state.showConfirmAppointAsMod = false;
1102 i.setState(i.state);
1105 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1106 i.state.showConfirmAppointAsAdmin = true;
1107 i.setState(i.state);
1110 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1111 i.state.showConfirmAppointAsAdmin = false;
1112 i.setState(i.state);
1115 handleAddAdmin(i: CommentNode) {
1116 let form: AddAdminForm = {
1117 user_id: i.props.node.comment.creator_id,
1120 WebSocketService.Instance.addAdmin(form);
1121 i.state.showConfirmAppointAsAdmin = false;
1122 i.setState(i.state);
1125 handleShowConfirmTransferCommunity(i: CommentNode) {
1126 i.state.showConfirmTransferCommunity = true;
1127 i.setState(i.state);
1130 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1131 i.state.showConfirmTransferCommunity = false;
1132 i.setState(i.state);
1135 handleTransferCommunity(i: CommentNode) {
1136 let form: TransferCommunityForm = {
1137 community_id: i.props.node.comment.community_id,
1138 user_id: i.props.node.comment.creator_id,
1140 WebSocketService.Instance.transferCommunity(form);
1141 i.state.showConfirmTransferCommunity = false;
1142 i.setState(i.state);
1145 handleShowConfirmTransferSite(i: CommentNode) {
1146 i.state.showConfirmTransferSite = true;
1147 i.setState(i.state);
1150 handleCancelShowConfirmTransferSite(i: CommentNode) {
1151 i.state.showConfirmTransferSite = false;
1152 i.setState(i.state);
1155 handleTransferSite(i: CommentNode) {
1156 let form: TransferSiteForm = {
1157 user_id: i.props.node.comment.creator_id,
1159 WebSocketService.Instance.transferSite(form);
1160 i.state.showConfirmTransferSite = false;
1161 i.setState(i.state);
1164 get isCommentNew(): boolean {
1165 let now = moment.utc().subtract(10, 'minutes');
1166 let then = moment.utc(this.props.node.comment.published);
1167 return now.isBefore(then);
1170 handleCommentCollapse(i: CommentNode) {
1171 i.state.collapsed = !i.state.collapsed;
1172 i.setState(i.state);
1175 handleViewSource(i: CommentNode) {
1176 i.state.viewSource = !i.state.viewSource;
1177 i.setState(i.state);
1180 handleShowAdvanced(i: CommentNode) {
1181 i.state.showAdvanced = !i.state.showAdvanced;
1182 i.setState(i.state);
1187 if (this.state.my_vote == 1) {
1189 } else if (this.state.my_vote == -1) {
1190 return 'text-danger';
1192 return 'text-muted';
1196 get pointsTippy(): string {
1197 let points = i18n.t('number_of_points', {
1198 count: this.state.score,
1201 let upvotes = i18n.t('number_of_upvotes', {
1202 count: this.state.upvotes,
1205 let downvotes = i18n.t('number_of_downvotes', {
1206 count: this.state.downvotes,
1209 return `${points} • ${upvotes} • ${downvotes}`;