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';
31 import moment from 'moment';
32 import { MomentTime } from './moment-time';
33 import { CommentForm } from './comment-form';
34 import { CommentNodes } from './comment-nodes';
35 import { UserListing } from './user-listing';
36 import { i18n } from '../i18next';
37 import { CakeDay } from './cake-day';
39 interface CommentNodeState {
42 showRemoveDialog: boolean;
44 showBanDialog: boolean;
48 showConfirmTransferSite: boolean;
49 showConfirmTransferCommunity: boolean;
50 showConfirmAppointAsMod: boolean;
51 showConfirmAppointAsAdmin: boolean;
54 showAdvanced: boolean;
64 interface CommentNodeProps {
70 showContext?: boolean;
71 moderators: Array<CommunityUser>;
72 admins: Array<UserView>;
73 // TODO is this necessary, can't I get it from the node itself?
74 postCreatorId?: number;
75 showCommunity?: boolean;
76 sort?: CommentSortType;
78 enableDownvotes: boolean;
81 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
82 private emptyState: CommentNodeState = {
85 showRemoveDialog: false,
90 banType: BanType.Community,
94 showConfirmTransferSite: false,
95 showConfirmTransferCommunity: false,
96 showConfirmAppointAsMod: false,
97 showConfirmAppointAsAdmin: false,
98 my_vote: this.props.node.comment.my_vote,
99 score: this.props.node.comment.score,
100 upvotes: this.props.node.comment.upvotes,
101 downvotes: this.props.node.comment.downvotes,
102 borderColor: this.props.node.comment.depth
103 ? colorList[this.props.node.comment.depth % colorList.length]
109 constructor(props: any, context: any) {
110 super(props, context);
112 this.state = this.emptyState;
113 this.handleReplyCancel = this.handleReplyCancel.bind(this);
114 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
115 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
118 componentWillReceiveProps(nextProps: CommentNodeProps) {
119 this.state.my_vote = nextProps.node.comment.my_vote;
120 this.state.upvotes = nextProps.node.comment.upvotes;
121 this.state.downvotes = nextProps.node.comment.downvotes;
122 this.state.score = nextProps.node.comment.score;
123 this.state.readLoading = false;
124 this.state.saveLoading = false;
125 this.setState(this.state);
129 let node = this.props.node;
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,
167 {isCakeDay(node.comment.creator_published) && (
168 <CakeDay creator_name={node.comment.creator_name} />
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>
194 <Link class="mr-2" to={`/c/${node.comment.community_name}`}>
195 {node.comment.community_name}
200 class="btn btn-sm text-muted"
201 onClick={linkEvent(this, this.handleCommentCollapse)}
203 {this.state.collapsed ? (
204 <svg class="icon icon-inline">
205 <use xlinkHref="#icon-plus-square"></use>
208 <svg class="icon icon-inline">
209 <use xlinkHref="#icon-minus-square"></use>
213 {/* This is an expanding spacer for mobile */}
214 <div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
216 className={`btn btn-sm p-0 unselectable pointer ${this.scoreColor}`}
217 onClick={linkEvent(node, this.handleCommentUpvote)}
218 data-tippy-content={this.pointsTippy}
220 <svg class="icon icon-inline mr-1">
221 <use xlinkHref="#icon-zap"></use>
223 <span class="mr-1">{this.state.score}</span>
225 <span className="mr-1">•</span>
227 <MomentTime data={node.comment} />
230 {/* end of user row */}
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 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
252 {this.props.showContext && this.linkBtn}
253 {this.props.markable && (
255 class="btn btn-link 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 ${
268 node.comment.read && 'text-success'
271 <use xlinkHref="#icon-check"></use>
276 {UserService.Instance.user && !this.props.viewOnly && (
279 className={`btn btn-link btn-animate ${
280 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
282 onClick={linkEvent(node, this.handleCommentUpvote)}
283 data-tippy-content={i18n.t('upvote')}
285 <svg class="icon icon-inline">
286 <use xlinkHref="#icon-arrow-up"></use>
288 {this.state.upvotes !== this.state.score && (
289 <span class="ml-1">{this.state.upvotes}</span>
292 {this.props.enableDownvotes && (
294 className={`btn btn-link btn-animate ${
295 this.state.my_vote == -1
299 onClick={linkEvent(node, this.handleCommentDownvote)}
300 data-tippy-content={i18n.t('downvote')}
302 <svg class="icon icon-inline">
303 <use xlinkHref="#icon-arrow-down"></use>
305 {this.state.upvotes !== this.state.score && (
306 <span class="ml-1">{this.state.downvotes}</span>
311 class="btn btn-link btn-animate text-muted"
312 onClick={linkEvent(this, this.handleReplyClick)}
313 data-tippy-content={i18n.t('reply')}
315 <svg class="icon icon-inline">
316 <use xlinkHref="#icon-reply1"></use>
319 {!this.state.showAdvanced ? (
321 className="btn btn-link btn-animate text-muted"
322 onClick={linkEvent(this, this.handleShowAdvanced)}
323 data-tippy-content={i18n.t('more')}
325 <svg class="icon icon-inline">
326 <use xlinkHref="#icon-more-vertical"></use>
331 {!this.myComment && (
332 <button class="btn btn-link btn-animate">
335 to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
336 title={i18n.t('message').toLowerCase()}
339 <use xlinkHref="#icon-mail"></use>
344 {!this.props.showContext && this.linkBtn}
346 class="btn btn-link btn-animate text-muted"
349 this.handleSaveCommentClick
357 {this.state.saveLoading ? (
361 class={`icon icon-inline ${
362 node.comment.saved && 'text-warning'
365 <use xlinkHref="#icon-star"></use>
370 className="btn btn-link btn-animate text-muted"
371 onClick={linkEvent(this, this.handleViewSource)}
372 data-tippy-content={i18n.t('view_source')}
375 class={`icon icon-inline ${
376 this.state.viewSource && 'text-success'
379 <use xlinkHref="#icon-file-text"></use>
385 class="btn btn-link btn-animate text-muted"
386 onClick={linkEvent(this, this.handleEditClick)}
387 data-tippy-content={i18n.t('edit')}
389 <svg class="icon icon-inline">
390 <use xlinkHref="#icon-edit"></use>
394 class="btn btn-link btn-animate text-muted"
397 this.handleDeleteClick
400 !node.comment.deleted
406 class={`icon icon-inline ${
407 node.comment.deleted && 'text-danger'
410 <use xlinkHref="#icon-trash"></use>
415 {/* Admins and mods can remove comments */}
416 {(this.canMod || this.canAdmin) && (
418 {!node.comment.removed ? (
420 class="btn btn-link btn-animate text-muted"
423 this.handleModRemoveShow
430 class="btn btn-link btn-animate text-muted"
433 this.handleModRemoveSubmit
441 {/* Mods can ban from community, and appoint as mods to community */}
445 (!node.comment.banned_from_community ? (
447 class="btn btn-link btn-animate text-muted"
450 this.handleModBanFromCommunityShow
457 class="btn btn-link btn-animate text-muted"
460 this.handleModBanFromCommunitySubmit
466 {!node.comment.banned_from_community &&
467 (!this.state.showConfirmAppointAsMod ? (
469 class="btn btn-link btn-animate text-muted"
472 this.handleShowConfirmAppointAsMod
476 ? i18n.t('remove_as_mod')
477 : i18n.t('appoint_as_mod')}
481 <button class="btn btn-link btn-animate text-muted">
482 {i18n.t('are_you_sure')}
485 class="btn btn-link btn-animate text-muted"
488 this.handleAddModToCommunity
494 class="btn btn-link btn-animate text-muted"
497 this.handleCancelConfirmAppointAsMod
506 {/* Community creators and admins can transfer community to another mod */}
507 {(this.amCommunityCreator || this.canAdmin) &&
509 (!this.state.showConfirmTransferCommunity ? (
511 class="btn btn-link btn-animate text-muted"
514 this.handleShowConfirmTransferCommunity
517 {i18n.t('transfer_community')}
521 <button class="btn btn-link btn-animate text-muted">
522 {i18n.t('are_you_sure')}
525 class="btn btn-link btn-animate text-muted"
528 this.handleTransferCommunity
534 class="btn btn-link btn-animate text-muted"
538 .handleCancelShowConfirmTransferCommunity
545 {/* Admins can ban from all, and appoint other admins */}
549 (!node.comment.banned ? (
551 class="btn btn-link btn-animate text-muted"
554 this.handleModBanShow
557 {i18n.t('ban_from_site')}
561 class="btn btn-link btn-animate text-muted"
564 this.handleModBanSubmit
567 {i18n.t('unban_from_site')}
570 {!node.comment.banned &&
571 (!this.state.showConfirmAppointAsAdmin ? (
573 class="btn btn-link btn-animate text-muted"
576 this.handleShowConfirmAppointAsAdmin
580 ? i18n.t('remove_as_admin')
581 : i18n.t('appoint_as_admin')}
585 <button class="btn btn-link btn-animate text-muted">
586 {i18n.t('are_you_sure')}
589 class="btn btn-link btn-animate text-muted"
598 class="btn btn-link btn-animate text-muted"
601 this.handleCancelConfirmAppointAsAdmin
610 {/* Site Creator can transfer to another admin */}
611 {this.amSiteCreator &&
613 (!this.state.showConfirmTransferSite ? (
615 class="btn btn-link btn-animate text-muted"
618 this.handleShowConfirmTransferSite
621 {i18n.t('transfer_site')}
625 <button class="btn btn-link btn-animate text-muted">
626 {i18n.t('are_you_sure')}
629 class="btn btn-link btn-animate text-muted"
632 this.handleTransferSite
638 class="btn btn-link btn-animate text-muted"
641 this.handleCancelShowConfirmTransferSite
653 {/* end of button group */}
658 {/* end of details */}
659 {this.state.showRemoveDialog && (
662 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
666 class="form-control mr-2"
667 placeholder={i18n.t('reason')}
668 value={this.state.removeReason}
669 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
671 <button type="submit" class="btn btn-secondary">
672 {i18n.t('remove_comment')}
676 {this.state.showBanDialog && (
677 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
678 <div class="form-group row">
679 <label class="col-form-label">{i18n.t('reason')}</label>
682 class="form-control mr-2"
683 placeholder={i18n.t('reason')}
684 value={this.state.banReason}
685 onInput={linkEvent(this, this.handleModBanReasonChange)}
688 {/* TODO hold off on expires until later */}
689 {/* <div class="form-group row"> */}
690 {/* <label class="col-form-label">Expires</label> */}
691 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
693 <div class="form-group row">
694 <button type="submit" class="btn btn-secondary">
695 {i18n.t('ban')} {node.comment.creator_name}
700 {this.state.showReply && (
703 onReplyCancel={this.handleReplyCancel}
704 disabled={this.props.locked}
707 {node.children && !this.state.collapsed && (
709 nodes={node.children}
710 locked={this.props.locked}
711 moderators={this.props.moderators}
712 admins={this.props.admins}
713 postCreatorId={this.props.postCreatorId}
714 sort={this.props.sort}
715 sortType={this.props.sortType}
716 enableDownvotes={this.props.enableDownvotes}
719 {/* A collapsed clearfix */}
720 {this.state.collapsed && <div class="row col-12"></div>}
726 let node = this.props.node;
729 class="btn btn-link btn-animate text-muted"
730 to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
731 title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')}
733 <svg class="icon icon-inline">
734 <use xlinkHref="#icon-link"></use>
742 <svg class="icon icon-spinner spin">
743 <use xlinkHref="#icon-spinner"></use>
748 get myComment(): boolean {
750 UserService.Instance.user &&
751 this.props.node.comment.creator_id == UserService.Instance.user.id
755 get isMod(): boolean {
757 this.props.moderators &&
759 this.props.moderators.map(m => m.user_id),
760 this.props.node.comment.creator_id
765 get isAdmin(): boolean {
769 this.props.admins.map(a => a.id),
770 this.props.node.comment.creator_id
775 get isPostCreator(): boolean {
776 return this.props.node.comment.creator_id == this.props.postCreatorId;
779 get canMod(): boolean {
780 if (this.props.admins && this.props.moderators) {
781 let adminsThenMods = this.props.admins
783 .concat(this.props.moderators.map(m => m.user_id));
786 UserService.Instance.user,
788 this.props.node.comment.creator_id
795 get canAdmin(): boolean {
799 UserService.Instance.user,
800 this.props.admins.map(a => a.id),
801 this.props.node.comment.creator_id
806 get amCommunityCreator(): boolean {
808 this.props.moderators &&
809 UserService.Instance.user &&
810 this.props.node.comment.creator_id != UserService.Instance.user.id &&
811 UserService.Instance.user.id == this.props.moderators[0].user_id
815 get amSiteCreator(): boolean {
818 UserService.Instance.user &&
819 this.props.node.comment.creator_id != UserService.Instance.user.id &&
820 UserService.Instance.user.id == this.props.admins[0].id
824 get commentUnlessRemoved(): string {
825 let node = this.props.node;
826 return node.comment.removed
827 ? `*${i18n.t('removed')}*`
828 : node.comment.deleted
829 ? `*${i18n.t('deleted')}*`
830 : node.comment.content;
833 handleReplyClick(i: CommentNode) {
834 i.state.showReply = true;
838 handleEditClick(i: CommentNode) {
839 i.state.showEdit = true;
843 handleDeleteClick(i: CommentNode) {
844 let deleteForm: CommentFormI = {
845 content: i.props.node.comment.content,
846 edit_id: i.props.node.comment.id,
847 creator_id: i.props.node.comment.creator_id,
848 post_id: i.props.node.comment.post_id,
849 parent_id: i.props.node.comment.parent_id,
850 deleted: !i.props.node.comment.deleted,
853 WebSocketService.Instance.editComment(deleteForm);
856 handleSaveCommentClick(i: CommentNode) {
858 i.props.node.comment.saved == undefined
860 : !i.props.node.comment.saved;
861 let form: SaveCommentForm = {
862 comment_id: i.props.node.comment.id,
866 WebSocketService.Instance.saveComment(form);
868 i.state.saveLoading = true;
869 i.setState(this.state);
872 handleReplyCancel() {
873 this.state.showReply = false;
874 this.state.showEdit = false;
875 this.setState(this.state);
878 handleCommentUpvote(i: CommentNodeI) {
879 let new_vote = this.state.my_vote == 1 ? 0 : 1;
881 if (this.state.my_vote == 1) {
883 this.state.upvotes--;
884 } else if (this.state.my_vote == -1) {
885 this.state.downvotes--;
886 this.state.upvotes++;
887 this.state.score += 2;
889 this.state.upvotes++;
893 this.state.my_vote = new_vote;
895 let form: CommentLikeForm = {
896 comment_id: i.comment.id,
897 post_id: i.comment.post_id,
898 score: this.state.my_vote,
901 WebSocketService.Instance.likeComment(form);
902 this.setState(this.state);
906 handleCommentDownvote(i: CommentNodeI) {
907 let new_vote = this.state.my_vote == -1 ? 0 : -1;
909 if (this.state.my_vote == 1) {
910 this.state.score -= 2;
911 this.state.upvotes--;
912 this.state.downvotes++;
913 } else if (this.state.my_vote == -1) {
914 this.state.downvotes--;
917 this.state.downvotes++;
921 this.state.my_vote = new_vote;
923 let form: CommentLikeForm = {
924 comment_id: i.comment.id,
925 post_id: i.comment.post_id,
926 score: this.state.my_vote,
929 WebSocketService.Instance.likeComment(form);
930 this.setState(this.state);
934 handleModRemoveShow(i: CommentNode) {
935 i.state.showRemoveDialog = true;
939 handleModRemoveReasonChange(i: CommentNode, event: any) {
940 i.state.removeReason = event.target.value;
944 handleModRemoveSubmit(i: CommentNode) {
945 event.preventDefault();
946 let form: CommentFormI = {
947 content: i.props.node.comment.content,
948 edit_id: i.props.node.comment.id,
949 creator_id: i.props.node.comment.creator_id,
950 post_id: i.props.node.comment.post_id,
951 parent_id: i.props.node.comment.parent_id,
952 removed: !i.props.node.comment.removed,
953 reason: i.state.removeReason,
956 WebSocketService.Instance.editComment(form);
958 i.state.showRemoveDialog = false;
962 handleMarkRead(i: CommentNode) {
963 // if it has a user_mention_id field, then its a mention
964 if (i.props.node.comment.user_mention_id) {
965 let form: EditUserMentionForm = {
966 user_mention_id: i.props.node.comment.user_mention_id,
967 read: !i.props.node.comment.read,
969 WebSocketService.Instance.editUserMention(form);
971 let form: CommentFormI = {
972 content: i.props.node.comment.content,
973 edit_id: i.props.node.comment.id,
974 creator_id: i.props.node.comment.creator_id,
975 post_id: i.props.node.comment.post_id,
976 parent_id: i.props.node.comment.parent_id,
977 read: !i.props.node.comment.read,
980 WebSocketService.Instance.editComment(form);
983 i.state.readLoading = true;
984 i.setState(this.state);
987 handleModBanFromCommunityShow(i: CommentNode) {
988 i.state.showBanDialog = !i.state.showBanDialog;
989 i.state.banType = BanType.Community;
993 handleModBanShow(i: CommentNode) {
994 i.state.showBanDialog = !i.state.showBanDialog;
995 i.state.banType = BanType.Site;
999 handleModBanReasonChange(i: CommentNode, event: any) {
1000 i.state.banReason = event.target.value;
1001 i.setState(i.state);
1004 handleModBanExpiresChange(i: CommentNode, event: any) {
1005 i.state.banExpires = event.target.value;
1006 i.setState(i.state);
1009 handleModBanFromCommunitySubmit(i: CommentNode) {
1010 i.state.banType = BanType.Community;
1011 i.setState(i.state);
1012 i.handleModBanBothSubmit(i);
1015 handleModBanSubmit(i: CommentNode) {
1016 i.state.banType = BanType.Site;
1017 i.setState(i.state);
1018 i.handleModBanBothSubmit(i);
1021 handleModBanBothSubmit(i: CommentNode) {
1022 event.preventDefault();
1024 if (i.state.banType == BanType.Community) {
1025 let form: BanFromCommunityForm = {
1026 user_id: i.props.node.comment.creator_id,
1027 community_id: i.props.node.comment.community_id,
1028 ban: !i.props.node.comment.banned_from_community,
1029 reason: i.state.banReason,
1030 expires: getUnixTime(i.state.banExpires),
1032 WebSocketService.Instance.banFromCommunity(form);
1034 let form: BanUserForm = {
1035 user_id: i.props.node.comment.creator_id,
1036 ban: !i.props.node.comment.banned,
1037 reason: i.state.banReason,
1038 expires: getUnixTime(i.state.banExpires),
1040 WebSocketService.Instance.banUser(form);
1043 i.state.showBanDialog = false;
1044 i.setState(i.state);
1047 handleShowConfirmAppointAsMod(i: CommentNode) {
1048 i.state.showConfirmAppointAsMod = true;
1049 i.setState(i.state);
1052 handleCancelConfirmAppointAsMod(i: CommentNode) {
1053 i.state.showConfirmAppointAsMod = false;
1054 i.setState(i.state);
1057 handleAddModToCommunity(i: CommentNode) {
1058 let form: AddModToCommunityForm = {
1059 user_id: i.props.node.comment.creator_id,
1060 community_id: i.props.node.comment.community_id,
1063 WebSocketService.Instance.addModToCommunity(form);
1064 i.state.showConfirmAppointAsMod = false;
1065 i.setState(i.state);
1068 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1069 i.state.showConfirmAppointAsAdmin = true;
1070 i.setState(i.state);
1073 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1074 i.state.showConfirmAppointAsAdmin = false;
1075 i.setState(i.state);
1078 handleAddAdmin(i: CommentNode) {
1079 let form: AddAdminForm = {
1080 user_id: i.props.node.comment.creator_id,
1083 WebSocketService.Instance.addAdmin(form);
1084 i.state.showConfirmAppointAsAdmin = false;
1085 i.setState(i.state);
1088 handleShowConfirmTransferCommunity(i: CommentNode) {
1089 i.state.showConfirmTransferCommunity = true;
1090 i.setState(i.state);
1093 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1094 i.state.showConfirmTransferCommunity = false;
1095 i.setState(i.state);
1098 handleTransferCommunity(i: CommentNode) {
1099 let form: TransferCommunityForm = {
1100 community_id: i.props.node.comment.community_id,
1101 user_id: i.props.node.comment.creator_id,
1103 WebSocketService.Instance.transferCommunity(form);
1104 i.state.showConfirmTransferCommunity = false;
1105 i.setState(i.state);
1108 handleShowConfirmTransferSite(i: CommentNode) {
1109 i.state.showConfirmTransferSite = true;
1110 i.setState(i.state);
1113 handleCancelShowConfirmTransferSite(i: CommentNode) {
1114 i.state.showConfirmTransferSite = false;
1115 i.setState(i.state);
1118 handleTransferSite(i: CommentNode) {
1119 let form: TransferSiteForm = {
1120 user_id: i.props.node.comment.creator_id,
1122 WebSocketService.Instance.transferSite(form);
1123 i.state.showConfirmTransferSite = false;
1124 i.setState(i.state);
1127 get isCommentNew(): boolean {
1128 let now = moment.utc().subtract(10, 'minutes');
1129 let then = moment.utc(this.props.node.comment.published);
1130 return now.isBefore(then);
1133 handleCommentCollapse(i: CommentNode) {
1134 i.state.collapsed = !i.state.collapsed;
1135 i.setState(i.state);
1138 handleViewSource(i: CommentNode) {
1139 i.state.viewSource = !i.state.viewSource;
1140 i.setState(i.state);
1143 handleShowAdvanced(i: CommentNode) {
1144 i.state.showAdvanced = !i.state.showAdvanced;
1145 i.setState(i.state);
1150 if (this.state.my_vote == 1) {
1152 } else if (this.state.my_vote == -1) {
1153 return 'text-danger';
1155 return 'text-muted';
1159 get pointsTippy(): string {
1160 let points = i18n.t('number_of_points', {
1161 count: this.state.score,
1164 let upvotes = i18n.t('number_of_upvotes', {
1165 count: this.state.upvotes,
1168 let downvotes = i18n.t('number_of_downvotes', {
1169 count: this.state.downvotes,
1172 return `${points} • ${upvotes} • ${downvotes}`;