1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
4 CommentNode as CommentNodeI,
9 MarkUserMentionAsReadForm,
15 AddModToCommunityForm,
17 TransferCommunityForm,
20 } from 'lemmy-js-client';
21 import { CommentSortType, BanType } from '../interfaces';
22 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 { CommunityLink } from './community-link';
37 import { i18n } from '../i18next';
39 interface CommentNodeState {
42 showRemoveDialog: boolean;
44 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: CommunityUser[];
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,
93 banType: BanType.Community,
97 showConfirmTransferSite: false,
98 showConfirmTransferCommunity: false,
99 showConfirmAppointAsMod: false,
100 showConfirmAppointAsAdmin: false,
101 my_vote: this.props.node.comment.my_vote,
102 score: this.props.node.comment.score,
103 upvotes: this.props.node.comment.upvotes,
104 downvotes: this.props.node.comment.downvotes,
105 borderColor: this.props.node.comment.depth
106 ? colorList[this.props.node.comment.depth % colorList.length]
112 constructor(props: any, context: any) {
113 super(props, context);
115 this.state = this.emptyState;
116 this.handleReplyCancel = this.handleReplyCancel.bind(this);
117 this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
118 this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
121 componentWillReceiveProps(nextProps: CommentNodeProps) {
122 this.state.my_vote = nextProps.node.comment.my_vote;
123 this.state.upvotes = nextProps.node.comment.upvotes;
124 this.state.downvotes = nextProps.node.comment.downvotes;
125 this.state.score = nextProps.node.comment.score;
126 this.state.readLoading = false;
127 this.state.saveLoading = false;
128 this.setState(this.state);
132 let node = this.props.node;
135 className={`comment ${
136 node.comment.parent_id && !this.props.noIndent ? 'ml-1' : ''
140 id={`comment-${node.comment.id}`}
141 className={`details comment-node py-2 ${
142 !this.props.noBorder ? 'border-top border-light' : ''
143 } ${this.isCommentNew ? 'mark' : ''}`}
145 !this.props.noIndent &&
146 this.props.node.comment.parent_id &&
147 `border-left: 2px ${this.state.borderColor} solid !important`
152 !this.props.noIndent &&
153 this.props.node.comment.parent_id &&
157 <div class="d-flex flex-wrap align-items-center text-muted small">
161 name: node.comment.creator_name,
162 preferred_username: node.comment.creator_preferred_username,
163 avatar: node.comment.creator_avatar,
164 id: node.comment.creator_id,
165 local: node.comment.creator_local,
166 actor_id: node.comment.creator_actor_id,
167 published: node.comment.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>
197 name: node.comment.community_name,
198 id: node.comment.community_id,
199 local: node.comment.community_local,
200 actor_id: node.comment.community_actor_id,
201 icon: node.comment.community_icon,
204 <span class="mx-2">•</span>
205 <Link className="mr-2" to={`/post/${node.comment.post_id}`}>
206 {node.comment.post_name}
211 class="btn btn-sm text-muted"
212 onClick={linkEvent(this, this.handleCommentCollapse)}
214 {this.state.collapsed ? '+' : '—'}
216 {/* This is an expanding spacer for mobile */}
217 <div className="mr-lg-4 flex-grow-1 flex-lg-grow-0 unselectable pointer mx-2"></div>
219 className={`unselectable pointer ${this.scoreColor}`}
220 onClick={linkEvent(node, this.handleCommentUpvote)}
221 data-tippy-content={this.pointsTippy}
223 <span class="mr-1 font-weight-bold">{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}
240 {!this.state.showEdit && !this.state.collapsed && (
242 {this.state.viewSource ? (
243 <pre>{this.commentUnlessRemoved}</pre>
247 dangerouslySetInnerHTML={mdToHtml(
248 this.commentUnlessRemoved
252 <div class="d-flex justify-content-between justify-content-lg-start flex-wrap text-muted font-weight-bold">
253 {this.props.showContext && this.linkBtn}
254 {this.props.markable && (
256 class="btn btn-link 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 ${
269 node.comment.read && 'text-success'
272 <use xlinkHref="#icon-check"></use>
277 {UserService.Instance.user && !this.props.viewOnly && (
280 className={`btn btn-link 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-up1"></use>
289 {this.state.upvotes !== this.state.score && (
290 <span class="ml-1">{this.state.upvotes}</span>
293 {this.props.enableDownvotes && (
295 className={`btn btn-link btn-animate ${
296 this.state.my_vote == -1
300 onClick={linkEvent(node, this.handleCommentDownvote)}
301 data-tippy-content={i18n.t('downvote')}
303 <svg class="icon icon-inline">
304 <use xlinkHref="#icon-arrow-down1"></use>
306 {this.state.upvotes !== this.state.score && (
307 <span class="ml-1">{this.state.downvotes}</span>
312 class="btn btn-link btn-animate text-muted"
313 onClick={linkEvent(this, this.handleReplyClick)}
314 data-tippy-content={i18n.t('reply')}
316 <svg class="icon icon-inline">
317 <use xlinkHref="#icon-reply1"></use>
320 {!this.state.showAdvanced ? (
322 className="btn btn-link btn-animate text-muted"
323 onClick={linkEvent(this, this.handleShowAdvanced)}
324 data-tippy-content={i18n.t('more')}
326 <svg class="icon icon-inline">
327 <use xlinkHref="#icon-more-vertical"></use>
332 {!this.myComment && (
333 <button class="btn btn-link btn-animate">
335 className="text-muted"
336 to={`/create_private_message/recipient/${node.comment.creator_id}`}
337 title={i18n.t('message').toLowerCase()}
340 <use xlinkHref="#icon-mail"></use>
345 {!this.props.showContext && this.linkBtn}
347 class="btn btn-link btn-animate text-muted"
350 this.handleSaveCommentClick
358 {this.state.saveLoading ? (
362 class={`icon icon-inline ${
363 node.comment.saved && 'text-warning'
366 <use xlinkHref="#icon-star"></use>
371 className="btn btn-link btn-animate text-muted"
372 onClick={linkEvent(this, this.handleViewSource)}
373 data-tippy-content={i18n.t('view_source')}
376 class={`icon icon-inline ${
377 this.state.viewSource && 'text-success'
380 <use xlinkHref="#icon-file-text"></use>
386 class="btn btn-link btn-animate text-muted"
387 onClick={linkEvent(this, this.handleEditClick)}
388 data-tippy-content={i18n.t('edit')}
390 <svg class="icon icon-inline">
391 <use xlinkHref="#icon-edit"></use>
395 class="btn btn-link btn-animate text-muted"
398 this.handleDeleteClick
401 !node.comment.deleted
407 class={`icon icon-inline ${
408 node.comment.deleted && 'text-danger'
411 <use xlinkHref="#icon-trash"></use>
416 {/* Admins and mods can remove comments */}
417 {(this.canMod || this.canAdmin) && (
419 {!node.comment.removed ? (
421 class="btn btn-link btn-animate text-muted"
424 this.handleModRemoveShow
431 class="btn btn-link btn-animate text-muted"
434 this.handleModRemoveSubmit
442 {/* Mods can ban from community, and appoint as mods to community */}
446 (!node.comment.banned_from_community ? (
448 class="btn btn-link btn-animate text-muted"
451 this.handleModBanFromCommunityShow
458 class="btn btn-link btn-animate text-muted"
461 this.handleModBanFromCommunitySubmit
467 {!node.comment.banned_from_community &&
468 node.comment.creator_local &&
469 (!this.state.showConfirmAppointAsMod ? (
471 class="btn btn-link btn-animate text-muted"
474 this.handleShowConfirmAppointAsMod
478 ? i18n.t('remove_as_mod')
479 : i18n.t('appoint_as_mod')}
483 <button class="btn btn-link btn-animate text-muted">
484 {i18n.t('are_you_sure')}
487 class="btn btn-link btn-animate text-muted"
490 this.handleAddModToCommunity
496 class="btn btn-link btn-animate text-muted"
499 this.handleCancelConfirmAppointAsMod
508 {/* Community creators and admins can transfer community to another mod */}
509 {(this.amCommunityCreator || this.canAdmin) &&
511 node.comment.creator_local &&
512 (!this.state.showConfirmTransferCommunity ? (
514 class="btn btn-link btn-animate text-muted"
517 this.handleShowConfirmTransferCommunity
520 {i18n.t('transfer_community')}
524 <button class="btn btn-link btn-animate text-muted">
525 {i18n.t('are_you_sure')}
528 class="btn btn-link btn-animate text-muted"
531 this.handleTransferCommunity
537 class="btn btn-link btn-animate text-muted"
541 .handleCancelShowConfirmTransferCommunity
548 {/* Admins can ban from all, and appoint other admins */}
552 (!node.comment.banned ? (
554 class="btn btn-link btn-animate text-muted"
557 this.handleModBanShow
560 {i18n.t('ban_from_site')}
564 class="btn btn-link btn-animate text-muted"
567 this.handleModBanSubmit
570 {i18n.t('unban_from_site')}
573 {!node.comment.banned &&
574 node.comment.creator_local &&
575 (!this.state.showConfirmAppointAsAdmin ? (
577 class="btn btn-link btn-animate text-muted"
580 this.handleShowConfirmAppointAsAdmin
584 ? i18n.t('remove_as_admin')
585 : i18n.t('appoint_as_admin')}
589 <button class="btn btn-link btn-animate text-muted">
590 {i18n.t('are_you_sure')}
593 class="btn btn-link btn-animate text-muted"
602 class="btn btn-link btn-animate text-muted"
605 this.handleCancelConfirmAppointAsAdmin
614 {/* Site Creator can transfer to another admin */}
615 {this.amSiteCreator &&
617 node.comment.creator_local &&
618 (!this.state.showConfirmTransferSite ? (
620 class="btn btn-link btn-animate text-muted"
623 this.handleShowConfirmTransferSite
626 {i18n.t('transfer_site')}
630 <button class="btn btn-link btn-animate text-muted">
631 {i18n.t('are_you_sure')}
634 class="btn btn-link btn-animate text-muted"
637 this.handleTransferSite
643 class="btn btn-link btn-animate text-muted"
646 this.handleCancelShowConfirmTransferSite
658 {/* end of button group */}
663 {/* end of details */}
664 {this.state.showRemoveDialog && (
667 onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
671 class="form-control mr-2"
672 placeholder={i18n.t('reason')}
673 value={this.state.removeReason}
674 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
676 <button type="submit" class="btn btn-secondary">
677 {i18n.t('remove_comment')}
681 {this.state.showBanDialog && (
682 <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
683 <div class="form-group row">
684 <label class="col-form-label">{i18n.t('reason')}</label>
687 class="form-control mr-2"
688 placeholder={i18n.t('reason')}
689 value={this.state.banReason}
690 onInput={linkEvent(this, this.handleModBanReasonChange)}
692 <div class="form-group">
693 <div class="form-check">
695 class="form-check-input"
696 id="mod-ban-remove-data"
698 checked={this.state.removeData}
699 onChange={linkEvent(this, this.handleModRemoveDataChange)}
701 <label class="form-check-label" htmlFor="mod-ban-remove-data">
702 {i18n.t('remove_posts_comments')}
707 {/* TODO hold off on expires until later */}
708 {/* <div class="form-group row"> */}
709 {/* <label class="col-form-label">Expires</label> */}
710 {/* <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
712 <div class="form-group row">
713 <button type="submit" class="btn btn-secondary">
714 {i18n.t('ban')} {node.comment.creator_name}
719 {this.state.showReply && (
722 onReplyCancel={this.handleReplyCancel}
723 disabled={this.props.locked}
727 {node.children && !this.state.collapsed && (
729 nodes={node.children}
730 locked={this.props.locked}
731 moderators={this.props.moderators}
732 admins={this.props.admins}
733 postCreatorId={this.props.postCreatorId}
734 sort={this.props.sort}
735 sortType={this.props.sortType}
736 enableDownvotes={this.props.enableDownvotes}
739 {/* A collapsed clearfix */}
740 {this.state.collapsed && <div class="row col-12"></div>}
746 let node = this.props.node;
749 className="btn btn-link btn-animate text-muted"
750 to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
751 title={this.props.showContext ? i18n.t('show_context') : i18n.t('link')}
753 <svg class="icon icon-inline">
754 <use xlinkHref="#icon-link"></use>
762 <svg class="icon icon-spinner spin">
763 <use xlinkHref="#icon-spinner"></use>
768 get myComment(): boolean {
770 UserService.Instance.user &&
771 this.props.node.comment.creator_id == UserService.Instance.user.id
775 get isMod(): boolean {
777 this.props.moderators &&
779 this.props.moderators.map(m => m.user_id),
780 this.props.node.comment.creator_id
785 get isAdmin(): boolean {
789 this.props.admins.map(a => a.id),
790 this.props.node.comment.creator_id
795 get isPostCreator(): boolean {
796 return this.props.node.comment.creator_id == this.props.postCreatorId;
799 get canMod(): boolean {
800 if (this.props.admins && this.props.moderators) {
801 let adminsThenMods = this.props.admins
803 .concat(this.props.moderators.map(m => m.user_id));
806 UserService.Instance.user,
808 this.props.node.comment.creator_id
815 get canAdmin(): boolean {
819 UserService.Instance.user,
820 this.props.admins.map(a => a.id),
821 this.props.node.comment.creator_id
826 get amCommunityCreator(): boolean {
828 this.props.moderators &&
829 UserService.Instance.user &&
830 this.props.node.comment.creator_id != UserService.Instance.user.id &&
831 UserService.Instance.user.id == this.props.moderators[0].user_id
835 get amSiteCreator(): boolean {
838 UserService.Instance.user &&
839 this.props.node.comment.creator_id != UserService.Instance.user.id &&
840 UserService.Instance.user.id == this.props.admins[0].id
844 get commentUnlessRemoved(): string {
845 let node = this.props.node;
846 return node.comment.removed
847 ? `*${i18n.t('removed')}*`
848 : node.comment.deleted
849 ? `*${i18n.t('deleted')}*`
850 : node.comment.content;
853 handleReplyClick(i: CommentNode) {
854 i.state.showReply = true;
858 handleEditClick(i: CommentNode) {
859 i.state.showEdit = true;
863 handleDeleteClick(i: CommentNode) {
864 let deleteForm: DeleteCommentForm = {
865 edit_id: i.props.node.comment.id,
866 deleted: !i.props.node.comment.deleted,
869 WebSocketService.Instance.deleteComment(deleteForm);
872 handleSaveCommentClick(i: CommentNode) {
874 i.props.node.comment.saved == undefined
876 : !i.props.node.comment.saved;
877 let form: SaveCommentForm = {
878 comment_id: i.props.node.comment.id,
882 WebSocketService.Instance.saveComment(form);
884 i.state.saveLoading = true;
885 i.setState(this.state);
888 handleReplyCancel() {
889 this.state.showReply = false;
890 this.state.showEdit = false;
891 this.setState(this.state);
894 handleCommentUpvote(i: CommentNodeI) {
895 let new_vote = this.state.my_vote == 1 ? 0 : 1;
897 if (this.state.my_vote == 1) {
899 this.state.upvotes--;
900 } else if (this.state.my_vote == -1) {
901 this.state.downvotes--;
902 this.state.upvotes++;
903 this.state.score += 2;
905 this.state.upvotes++;
909 this.state.my_vote = new_vote;
911 let form: CommentLikeForm = {
912 comment_id: i.comment.id,
913 score: this.state.my_vote,
916 WebSocketService.Instance.likeComment(form);
917 this.setState(this.state);
921 handleCommentDownvote(i: CommentNodeI) {
922 let new_vote = this.state.my_vote == -1 ? 0 : -1;
924 if (this.state.my_vote == 1) {
925 this.state.score -= 2;
926 this.state.upvotes--;
927 this.state.downvotes++;
928 } else if (this.state.my_vote == -1) {
929 this.state.downvotes--;
932 this.state.downvotes++;
936 this.state.my_vote = new_vote;
938 let form: CommentLikeForm = {
939 comment_id: i.comment.id,
940 score: this.state.my_vote,
943 WebSocketService.Instance.likeComment(form);
944 this.setState(this.state);
948 handleModRemoveShow(i: CommentNode) {
949 i.state.showRemoveDialog = true;
953 handleModRemoveReasonChange(i: CommentNode, event: any) {
954 i.state.removeReason = event.target.value;
958 handleModRemoveDataChange(i: CommentNode, event: any) {
959 i.state.removeData = event.target.checked;
963 handleModRemoveSubmit(i: CommentNode) {
964 event.preventDefault();
965 let form: RemoveCommentForm = {
966 edit_id: i.props.node.comment.id,
967 removed: !i.props.node.comment.removed,
968 reason: i.state.removeReason,
971 WebSocketService.Instance.removeComment(form);
973 i.state.showRemoveDialog = false;
977 handleMarkRead(i: CommentNode) {
978 // if it has a user_mention_id field, then its a mention
979 if (i.props.node.comment.user_mention_id) {
980 let form: MarkUserMentionAsReadForm = {
981 user_mention_id: i.props.node.comment.user_mention_id,
982 read: !i.props.node.comment.read,
984 WebSocketService.Instance.markUserMentionAsRead(form);
986 let form: MarkCommentAsReadForm = {
987 edit_id: i.props.node.comment.id,
988 read: !i.props.node.comment.read,
991 WebSocketService.Instance.markCommentAsRead(form);
994 i.state.readLoading = true;
995 i.setState(this.state);
998 handleModBanFromCommunityShow(i: CommentNode) {
999 i.state.showBanDialog = !i.state.showBanDialog;
1000 i.state.banType = BanType.Community;
1001 i.setState(i.state);
1004 handleModBanShow(i: CommentNode) {
1005 i.state.showBanDialog = !i.state.showBanDialog;
1006 i.state.banType = BanType.Site;
1007 i.setState(i.state);
1010 handleModBanReasonChange(i: CommentNode, event: any) {
1011 i.state.banReason = event.target.value;
1012 i.setState(i.state);
1015 handleModBanExpiresChange(i: CommentNode, event: any) {
1016 i.state.banExpires = event.target.value;
1017 i.setState(i.state);
1020 handleModBanFromCommunitySubmit(i: CommentNode) {
1021 i.state.banType = BanType.Community;
1022 i.setState(i.state);
1023 i.handleModBanBothSubmit(i);
1026 handleModBanSubmit(i: CommentNode) {
1027 i.state.banType = BanType.Site;
1028 i.setState(i.state);
1029 i.handleModBanBothSubmit(i);
1032 handleModBanBothSubmit(i: CommentNode) {
1033 event.preventDefault();
1035 if (i.state.banType == BanType.Community) {
1036 // If its an unban, restore all their data
1037 let ban = !i.props.node.comment.banned_from_community;
1039 i.state.removeData = false;
1041 let form: BanFromCommunityForm = {
1042 user_id: i.props.node.comment.creator_id,
1043 community_id: i.props.node.comment.community_id,
1045 remove_data: i.state.removeData,
1046 reason: i.state.banReason,
1047 expires: getUnixTime(i.state.banExpires),
1049 WebSocketService.Instance.banFromCommunity(form);
1051 // If its an unban, restore all their data
1052 let ban = !i.props.node.comment.banned;
1054 i.state.removeData = false;
1056 let form: BanUserForm = {
1057 user_id: i.props.node.comment.creator_id,
1059 remove_data: i.state.removeData,
1060 reason: i.state.banReason,
1061 expires: getUnixTime(i.state.banExpires),
1063 WebSocketService.Instance.banUser(form);
1066 i.state.showBanDialog = false;
1067 i.setState(i.state);
1070 handleShowConfirmAppointAsMod(i: CommentNode) {
1071 i.state.showConfirmAppointAsMod = true;
1072 i.setState(i.state);
1075 handleCancelConfirmAppointAsMod(i: CommentNode) {
1076 i.state.showConfirmAppointAsMod = false;
1077 i.setState(i.state);
1080 handleAddModToCommunity(i: CommentNode) {
1081 let form: AddModToCommunityForm = {
1082 user_id: i.props.node.comment.creator_id,
1083 community_id: i.props.node.comment.community_id,
1086 WebSocketService.Instance.addModToCommunity(form);
1087 i.state.showConfirmAppointAsMod = false;
1088 i.setState(i.state);
1091 handleShowConfirmAppointAsAdmin(i: CommentNode) {
1092 i.state.showConfirmAppointAsAdmin = true;
1093 i.setState(i.state);
1096 handleCancelConfirmAppointAsAdmin(i: CommentNode) {
1097 i.state.showConfirmAppointAsAdmin = false;
1098 i.setState(i.state);
1101 handleAddAdmin(i: CommentNode) {
1102 let form: AddAdminForm = {
1103 user_id: i.props.node.comment.creator_id,
1106 WebSocketService.Instance.addAdmin(form);
1107 i.state.showConfirmAppointAsAdmin = false;
1108 i.setState(i.state);
1111 handleShowConfirmTransferCommunity(i: CommentNode) {
1112 i.state.showConfirmTransferCommunity = true;
1113 i.setState(i.state);
1116 handleCancelShowConfirmTransferCommunity(i: CommentNode) {
1117 i.state.showConfirmTransferCommunity = false;
1118 i.setState(i.state);
1121 handleTransferCommunity(i: CommentNode) {
1122 let form: TransferCommunityForm = {
1123 community_id: i.props.node.comment.community_id,
1124 user_id: i.props.node.comment.creator_id,
1126 WebSocketService.Instance.transferCommunity(form);
1127 i.state.showConfirmTransferCommunity = false;
1128 i.setState(i.state);
1131 handleShowConfirmTransferSite(i: CommentNode) {
1132 i.state.showConfirmTransferSite = true;
1133 i.setState(i.state);
1136 handleCancelShowConfirmTransferSite(i: CommentNode) {
1137 i.state.showConfirmTransferSite = false;
1138 i.setState(i.state);
1141 handleTransferSite(i: CommentNode) {
1142 let form: TransferSiteForm = {
1143 user_id: i.props.node.comment.creator_id,
1145 WebSocketService.Instance.transferSite(form);
1146 i.state.showConfirmTransferSite = false;
1147 i.setState(i.state);
1150 get isCommentNew(): boolean {
1151 let now = moment.utc().subtract(10, 'minutes');
1152 let then = moment.utc(this.props.node.comment.published);
1153 return now.isBefore(then);
1156 handleCommentCollapse(i: CommentNode) {
1157 i.state.collapsed = !i.state.collapsed;
1158 i.setState(i.state);
1161 handleViewSource(i: CommentNode) {
1162 i.state.viewSource = !i.state.viewSource;
1163 i.setState(i.state);
1166 handleShowAdvanced(i: CommentNode) {
1167 i.state.showAdvanced = !i.state.showAdvanced;
1168 i.setState(i.state);
1173 if (this.state.my_vote == 1) {
1175 } else if (this.state.my_vote == -1) {
1176 return 'text-danger';
1178 return 'text-muted';
1182 get pointsTippy(): string {
1183 let points = i18n.t('number_of_points', {
1184 count: this.state.score,
1187 let upvotes = i18n.t('number_of_upvotes', {
1188 count: this.state.upvotes,
1191 let downvotes = i18n.t('number_of_downvotes', {
1192 count: this.state.downvotes,
1195 return `${points} • ${upvotes} • ${downvotes}`;