]> Untitled Git - lemmy.git/blob - ui/src/components/comment-node.tsx
Merge branch 'dev' into federation
[lemmy.git] / ui / src / components / comment-node.tsx
1 import { Component, linkEvent } from 'inferno';
2 import { Link } from 'inferno-router';
3 import {
4   CommentNode as CommentNodeI,
5   CommentLikeForm,
6   CommentForm as CommentFormI,
7   EditUserMentionForm,
8   SaveCommentForm,
9   BanFromCommunityForm,
10   BanUserForm,
11   CommunityUser,
12   UserView,
13   AddModToCommunityForm,
14   AddAdminForm,
15   TransferCommunityForm,
16   TransferSiteForm,
17   BanType,
18   CommentSortType,
19   SortType,
20 } from '../interfaces';
21 import { WebSocketService, UserService } from '../services';
22 import {
23   mdToHtml,
24   getUnixTime,
25   canMod,
26   isMod,
27   pictshareAvatarThumbnail,
28   showAvatars,
29 } from '../utils';
30 import moment from 'moment';
31 import { MomentTime } from './moment-time';
32 import { CommentForm } from './comment-form';
33 import { CommentNodes } from './comment-nodes';
34 import { i18n } from '../i18next';
35
36 interface CommentNodeState {
37   showReply: boolean;
38   showEdit: boolean;
39   showRemoveDialog: boolean;
40   removeReason: string;
41   showBanDialog: boolean;
42   banReason: string;
43   banExpires: string;
44   banType: BanType;
45   showConfirmTransferSite: boolean;
46   showConfirmTransferCommunity: boolean;
47   showConfirmAppointAsMod: boolean;
48   showConfirmAppointAsAdmin: boolean;
49   collapsed: boolean;
50   viewSource: boolean;
51   my_vote: number;
52   score: number;
53   upvotes: number;
54   downvotes: number;
55 }
56
57 interface CommentNodeProps {
58   node: CommentNodeI;
59   noIndent?: boolean;
60   viewOnly?: boolean;
61   locked?: boolean;
62   markable?: boolean;
63   moderators: Array<CommunityUser>;
64   admins: Array<UserView>;
65   // TODO is this necessary, can't I get it from the node itself?
66   postCreatorId?: number;
67   showCommunity?: boolean;
68   sort?: CommentSortType;
69   sortType?: SortType;
70 }
71
72 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
73   private emptyState: CommentNodeState = {
74     showReply: false,
75     showEdit: false,
76     showRemoveDialog: false,
77     removeReason: null,
78     showBanDialog: false,
79     banReason: null,
80     banExpires: null,
81     banType: BanType.Community,
82     collapsed: false,
83     viewSource: false,
84     showConfirmTransferSite: false,
85     showConfirmTransferCommunity: false,
86     showConfirmAppointAsMod: false,
87     showConfirmAppointAsAdmin: false,
88     my_vote: this.props.node.comment.my_vote,
89     score: this.props.node.comment.score,
90     upvotes: this.props.node.comment.upvotes,
91     downvotes: this.props.node.comment.downvotes,
92   };
93
94   constructor(props: any, context: any) {
95     super(props, context);
96
97     this.state = this.emptyState;
98     this.handleReplyCancel = this.handleReplyCancel.bind(this);
99     this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
100     this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
101   }
102
103   componentWillReceiveProps(nextProps: CommentNodeProps) {
104     this.state.my_vote = nextProps.node.comment.my_vote;
105     this.state.upvotes = nextProps.node.comment.upvotes;
106     this.state.downvotes = nextProps.node.comment.downvotes;
107     this.state.score = nextProps.node.comment.score;
108     this.setState(this.state);
109   }
110
111   render() {
112     let node = this.props.node;
113     return (
114       <div
115         className={`comment ${
116           node.comment.parent_id && !this.props.noIndent ? 'ml-4' : ''
117         }`}
118       >
119         {!this.state.collapsed && (
120           <div
121             className={`vote-bar mr-2 float-left small text-center ${this.props
122               .viewOnly && 'no-click'}`}
123           >
124             <button
125               className={`vote-animate btn btn-link p-0 ${
126                 this.state.my_vote == 1 ? 'text-info' : 'text-muted'
127               }`}
128               onClick={linkEvent(node, this.handleCommentUpvote)}
129             >
130               <svg class="icon upvote">
131                 <use xlinkHref="#icon-arrow-up"></use>
132               </svg>
133             </button>
134             <div class={`font-weight-bold text-muted`}>{this.state.score}</div>
135             {WebSocketService.Instance.site.enable_downvotes && (
136               <button
137                 className={`vote-animate btn btn-link p-0 ${
138                   this.state.my_vote == -1 ? 'text-danger' : 'text-muted'
139                 }`}
140                 onClick={linkEvent(node, this.handleCommentDownvote)}
141               >
142                 <svg class="icon downvote">
143                   <use xlinkHref="#icon-arrow-down"></use>
144                 </svg>
145               </button>
146             )}
147           </div>
148         )}
149         <div
150           id={`comment-${node.comment.id}`}
151           className={`details comment-node ml-4 ${
152             this.isCommentNew ? 'mark' : ''
153           }`}
154         >
155           <ul class="list-inline mb-0 text-muted small">
156             <li className="list-inline-item">
157               <Link
158                 className="text-info"
159                 to={`/u/${node.comment.creator_name}`}
160               >
161                 {node.comment.creator_avatar && showAvatars() && (
162                   <img
163                     height="32"
164                     width="32"
165                     src={pictshareAvatarThumbnail(node.comment.creator_avatar)}
166                     class="rounded-circle mr-1"
167                   />
168                 )}
169                 <span>{node.comment.creator_name}</span>
170               </Link>
171             </li>
172             {this.isMod && (
173               <li className="list-inline-item badge badge-light">
174                 {i18n.t('mod')}
175               </li>
176             )}
177             {this.isAdmin && (
178               <li className="list-inline-item badge badge-light">
179                 {i18n.t('admin')}
180               </li>
181             )}
182             {this.isPostCreator && (
183               <li className="list-inline-item badge badge-light">
184                 {i18n.t('creator')}
185               </li>
186             )}
187             {(node.comment.banned_from_community || node.comment.banned) && (
188               <li className="list-inline-item badge badge-danger">
189                 {i18n.t('banned')}
190               </li>
191             )}
192             <li className="list-inline-item">
193               <span>
194                 (<span className="text-info">+{this.state.upvotes}</span>
195                 <span> | </span>
196                 <span className="text-danger">-{this.state.downvotes}</span>
197                 <span>) </span>
198               </span>
199             </li>
200             {this.props.showCommunity && (
201               <li className="list-inline-item">
202                 <span> {i18n.t('to')} </span>
203                 <Link to={`/c/${node.comment.community_name}`}>
204                   {node.comment.community_name}
205                 </Link>
206               </li>
207             )}
208             <li className="list-inline-item">
209               <span>
210                 <MomentTime data={node.comment} />
211               </span>
212             </li>
213             <li className="list-inline-item">
214               <div
215                 className="pointer text-monospace"
216                 onClick={linkEvent(this, this.handleCommentCollapse)}
217               >
218                 {this.state.collapsed ? '[+]' : '[-]'}
219               </div>
220             </li>
221           </ul>
222           {this.state.showEdit && (
223             <CommentForm
224               node={node}
225               edit
226               onReplyCancel={this.handleReplyCancel}
227               disabled={this.props.locked}
228             />
229           )}
230           {!this.state.showEdit && !this.state.collapsed && (
231             <div>
232               {this.state.viewSource ? (
233                 <pre>{this.commentUnlessRemoved}</pre>
234               ) : (
235                 <div
236                   className="md-div"
237                   dangerouslySetInnerHTML={mdToHtml(this.commentUnlessRemoved)}
238                 />
239               )}
240               <ul class="list-inline mb-1 text-muted small font-weight-bold">
241                 {this.props.markable && (
242                   <li className="list-inline-item">
243                     <span
244                       class="pointer"
245                       onClick={linkEvent(this, this.handleMarkRead)}
246                     >
247                       {node.comment.read
248                         ? i18n.t('mark_as_unread')
249                         : i18n.t('mark_as_read')}
250                     </span>
251                   </li>
252                 )}
253                 {UserService.Instance.user && !this.props.viewOnly && (
254                   <>
255                     <li className="list-inline-item">
256                       <span
257                         class="pointer"
258                         onClick={linkEvent(this, this.handleReplyClick)}
259                       >
260                         {i18n.t('reply')}
261                       </span>
262                     </li>
263                     <li className="list-inline-item mr-2">
264                       <span
265                         class="pointer"
266                         onClick={linkEvent(this, this.handleSaveCommentClick)}
267                       >
268                         {node.comment.saved ? i18n.t('unsave') : i18n.t('save')}
269                       </span>
270                     </li>
271                     {this.myComment && (
272                       <>
273                         <li className="list-inline-item">
274                           <span
275                             class="pointer"
276                             onClick={linkEvent(this, this.handleEditClick)}
277                           >
278                             {i18n.t('edit')}
279                           </span>
280                         </li>
281                         <li className="list-inline-item">
282                           <span
283                             class="pointer"
284                             onClick={linkEvent(this, this.handleDeleteClick)}
285                           >
286                             {!node.comment.deleted
287                               ? i18n.t('delete')
288                               : i18n.t('restore')}
289                           </span>
290                         </li>
291                       </>
292                     )}
293                     {!this.myComment && (
294                       <li className="list-inline-item">
295                         <Link
296                           class="text-muted"
297                           to={`/create_private_message?recipient_id=${node.comment.creator_id}`}
298                         >
299                           {i18n.t('message').toLowerCase()}
300                         </Link>
301                       </li>
302                     )}
303                     <li className="list-inline-item">•</li>
304                     <li className="list-inline-item">
305                       <span
306                         className="pointer"
307                         onClick={linkEvent(this, this.handleViewSource)}
308                       >
309                         {i18n.t('view_source')}
310                       </span>
311                     </li>
312                     <li className="list-inline-item">
313                       <Link
314                         className="text-muted"
315                         to={`/post/${node.comment.post_id}/comment/${node.comment.id}`}
316                       >
317                         {i18n.t('link')}
318                       </Link>
319                     </li>
320                     {/* Admins and mods can remove comments */}
321                     {(this.canMod || this.canAdmin) && (
322                       <>
323                         <li className="list-inline-item">•</li>
324                         <li className="list-inline-item">
325                           {!node.comment.removed ? (
326                             <span
327                               class="pointer"
328                               onClick={linkEvent(
329                                 this,
330                                 this.handleModRemoveShow
331                               )}
332                             >
333                               {i18n.t('remove')}
334                             </span>
335                           ) : (
336                             <span
337                               class="pointer"
338                               onClick={linkEvent(
339                                 this,
340                                 this.handleModRemoveSubmit
341                               )}
342                             >
343                               {i18n.t('restore')}
344                             </span>
345                           )}
346                         </li>
347                       </>
348                     )}
349                     {/* Mods can ban from community, and appoint as mods to community */}
350                     {this.canMod && (
351                       <>
352                         {!this.isMod && (
353                           <li className="list-inline-item">
354                             {!node.comment.banned_from_community ? (
355                               <span
356                                 class="pointer"
357                                 onClick={linkEvent(
358                                   this,
359                                   this.handleModBanFromCommunityShow
360                                 )}
361                               >
362                                 {i18n.t('ban')}
363                               </span>
364                             ) : (
365                               <span
366                                 class="pointer"
367                                 onClick={linkEvent(
368                                   this,
369                                   this.handleModBanFromCommunitySubmit
370                                 )}
371                               >
372                                 {i18n.t('unban')}
373                               </span>
374                             )}
375                           </li>
376                         )}
377                         {!node.comment.banned_from_community && (
378                           <li className="list-inline-item">
379                             {!this.state.showConfirmAppointAsMod ? (
380                               <span
381                                 class="pointer"
382                                 onClick={linkEvent(
383                                   this,
384                                   this.handleShowConfirmAppointAsMod
385                                 )}
386                               >
387                                 {this.isMod
388                                   ? i18n.t('remove_as_mod')
389                                   : i18n.t('appoint_as_mod')}
390                               </span>
391                             ) : (
392                               <>
393                                 <span class="d-inline-block mr-1">
394                                   {i18n.t('are_you_sure')}
395                                 </span>
396                                 <span
397                                   class="pointer d-inline-block mr-1"
398                                   onClick={linkEvent(
399                                     this,
400                                     this.handleAddModToCommunity
401                                   )}
402                                 >
403                                   {i18n.t('yes')}
404                                 </span>
405                                 <span
406                                   class="pointer d-inline-block"
407                                   onClick={linkEvent(
408                                     this,
409                                     this.handleCancelConfirmAppointAsMod
410                                   )}
411                                 >
412                                   {i18n.t('no')}
413                                 </span>
414                               </>
415                             )}
416                           </li>
417                         )}
418                       </>
419                     )}
420                     {/* Community creators and admins can transfer community to another mod */}
421                     {(this.amCommunityCreator || this.canAdmin) && this.isMod && (
422                       <li className="list-inline-item">
423                         {!this.state.showConfirmTransferCommunity ? (
424                           <span
425                             class="pointer"
426                             onClick={linkEvent(
427                               this,
428                               this.handleShowConfirmTransferCommunity
429                             )}
430                           >
431                             {i18n.t('transfer_community')}
432                           </span>
433                         ) : (
434                           <>
435                             <span class="d-inline-block mr-1">
436                               {i18n.t('are_you_sure')}
437                             </span>
438                             <span
439                               class="pointer d-inline-block mr-1"
440                               onClick={linkEvent(
441                                 this,
442                                 this.handleTransferCommunity
443                               )}
444                             >
445                               {i18n.t('yes')}
446                             </span>
447                             <span
448                               class="pointer d-inline-block"
449                               onClick={linkEvent(
450                                 this,
451                                 this.handleCancelShowConfirmTransferCommunity
452                               )}
453                             >
454                               {i18n.t('no')}
455                             </span>
456                           </>
457                         )}
458                       </li>
459                     )}
460                     {/* Admins can ban from all, and appoint other admins */}
461                     {this.canAdmin && (
462                       <>
463                         {!this.isAdmin && (
464                           <li className="list-inline-item">
465                             {!node.comment.banned ? (
466                               <span
467                                 class="pointer"
468                                 onClick={linkEvent(this, this.handleModBanShow)}
469                               >
470                                 {i18n.t('ban_from_site')}
471                               </span>
472                             ) : (
473                               <span
474                                 class="pointer"
475                                 onClick={linkEvent(
476                                   this,
477                                   this.handleModBanSubmit
478                                 )}
479                               >
480                                 {i18n.t('unban_from_site')}
481                               </span>
482                             )}
483                           </li>
484                         )}
485                         {!node.comment.banned && (
486                           <li className="list-inline-item">
487                             {!this.state.showConfirmAppointAsAdmin ? (
488                               <span
489                                 class="pointer"
490                                 onClick={linkEvent(
491                                   this,
492                                   this.handleShowConfirmAppointAsAdmin
493                                 )}
494                               >
495                                 {this.isAdmin
496                                   ? i18n.t('remove_as_admin')
497                                   : i18n.t('appoint_as_admin')}
498                               </span>
499                             ) : (
500                               <>
501                                 <span class="d-inline-block mr-1">
502                                   {i18n.t('are_you_sure')}
503                                 </span>
504                                 <span
505                                   class="pointer d-inline-block mr-1"
506                                   onClick={linkEvent(this, this.handleAddAdmin)}
507                                 >
508                                   {i18n.t('yes')}
509                                 </span>
510                                 <span
511                                   class="pointer d-inline-block"
512                                   onClick={linkEvent(
513                                     this,
514                                     this.handleCancelConfirmAppointAsAdmin
515                                   )}
516                                 >
517                                   {i18n.t('no')}
518                                 </span>
519                               </>
520                             )}
521                           </li>
522                         )}
523                       </>
524                     )}
525                     {/* Site Creator can transfer to another admin */}
526                     {this.amSiteCreator && this.isAdmin && (
527                       <li className="list-inline-item">
528                         {!this.state.showConfirmTransferSite ? (
529                           <span
530                             class="pointer"
531                             onClick={linkEvent(
532                               this,
533                               this.handleShowConfirmTransferSite
534                             )}
535                           >
536                             {i18n.t('transfer_site')}
537                           </span>
538                         ) : (
539                           <>
540                             <span class="d-inline-block mr-1">
541                               {i18n.t('are_you_sure')}
542                             </span>
543                             <span
544                               class="pointer d-inline-block mr-1"
545                               onClick={linkEvent(this, this.handleTransferSite)}
546                             >
547                               {i18n.t('yes')}
548                             </span>
549                             <span
550                               class="pointer d-inline-block"
551                               onClick={linkEvent(
552                                 this,
553                                 this.handleCancelShowConfirmTransferSite
554                               )}
555                             >
556                               {i18n.t('no')}
557                             </span>
558                           </>
559                         )}
560                       </li>
561                     )}
562                   </>
563                 )}
564               </ul>
565             </div>
566           )}
567         </div>
568         {this.state.showRemoveDialog && (
569           <form
570             class="form-inline"
571             onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
572           >
573             <input
574               type="text"
575               class="form-control mr-2"
576               placeholder={i18n.t('reason')}
577               value={this.state.removeReason}
578               onInput={linkEvent(this, this.handleModRemoveReasonChange)}
579             />
580             <button type="submit" class="btn btn-secondary">
581               {i18n.t('remove_comment')}
582             </button>
583           </form>
584         )}
585         {this.state.showBanDialog && (
586           <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
587             <div class="form-group row">
588               <label class="col-form-label">{i18n.t('reason')}</label>
589               <input
590                 type="text"
591                 class="form-control mr-2"
592                 placeholder={i18n.t('reason')}
593                 value={this.state.banReason}
594                 onInput={linkEvent(this, this.handleModBanReasonChange)}
595               />
596             </div>
597             {/* TODO hold off on expires until later */}
598             {/* <div class="form-group row"> */}
599             {/*   <label class="col-form-label">Expires</label> */}
600             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
601             {/* </div> */}
602             <div class="form-group row">
603               <button type="submit" class="btn btn-secondary">
604                 {i18n.t('ban')} {node.comment.creator_name}
605               </button>
606             </div>
607           </form>
608         )}
609         {this.state.showReply && (
610           <CommentForm
611             node={node}
612             onReplyCancel={this.handleReplyCancel}
613             disabled={this.props.locked}
614           />
615         )}
616         {node.children && !this.state.collapsed && (
617           <CommentNodes
618             nodes={node.children}
619             locked={this.props.locked}
620             moderators={this.props.moderators}
621             admins={this.props.admins}
622             postCreatorId={this.props.postCreatorId}
623             sort={this.props.sort}
624             sortType={this.props.sortType}
625           />
626         )}
627         {/* A collapsed clearfix */}
628         {this.state.collapsed && <div class="row col-12"></div>}
629       </div>
630     );
631   }
632
633   get myComment(): boolean {
634     return (
635       UserService.Instance.user &&
636       this.props.node.comment.creator_id == UserService.Instance.user.id
637     );
638   }
639
640   get isMod(): boolean {
641     return (
642       this.props.moderators &&
643       isMod(
644         this.props.moderators.map(m => m.user_id),
645         this.props.node.comment.creator_id
646       )
647     );
648   }
649
650   get isAdmin(): boolean {
651     return (
652       this.props.admins &&
653       isMod(
654         this.props.admins.map(a => a.id),
655         this.props.node.comment.creator_id
656       )
657     );
658   }
659
660   get isPostCreator(): boolean {
661     return this.props.node.comment.creator_id == this.props.postCreatorId;
662   }
663
664   get canMod(): boolean {
665     if (this.props.admins && this.props.moderators) {
666       let adminsThenMods = this.props.admins
667         .map(a => a.id)
668         .concat(this.props.moderators.map(m => m.user_id));
669
670       return canMod(
671         UserService.Instance.user,
672         adminsThenMods,
673         this.props.node.comment.creator_id
674       );
675     } else {
676       return false;
677     }
678   }
679
680   get canAdmin(): boolean {
681     return (
682       this.props.admins &&
683       canMod(
684         UserService.Instance.user,
685         this.props.admins.map(a => a.id),
686         this.props.node.comment.creator_id
687       )
688     );
689   }
690
691   get amCommunityCreator(): boolean {
692     return (
693       this.props.moderators &&
694       UserService.Instance.user &&
695       this.props.node.comment.creator_id != UserService.Instance.user.id &&
696       UserService.Instance.user.id == this.props.moderators[0].user_id
697     );
698   }
699
700   get amSiteCreator(): boolean {
701     return (
702       this.props.admins &&
703       UserService.Instance.user &&
704       this.props.node.comment.creator_id != UserService.Instance.user.id &&
705       UserService.Instance.user.id == this.props.admins[0].id
706     );
707   }
708
709   get commentUnlessRemoved(): string {
710     let node = this.props.node;
711     return node.comment.removed
712       ? `*${i18n.t('removed')}*`
713       : node.comment.deleted
714       ? `*${i18n.t('deleted')}*`
715       : node.comment.content;
716   }
717
718   handleReplyClick(i: CommentNode) {
719     i.state.showReply = true;
720     i.setState(i.state);
721   }
722
723   handleEditClick(i: CommentNode) {
724     i.state.showEdit = true;
725     i.setState(i.state);
726   }
727
728   handleDeleteClick(i: CommentNode) {
729     let deleteForm: CommentFormI = {
730       content: i.props.node.comment.content,
731       edit_id: i.props.node.comment.id,
732       creator_id: i.props.node.comment.creator_id,
733       post_id: i.props.node.comment.post_id,
734       parent_id: i.props.node.comment.parent_id,
735       deleted: !i.props.node.comment.deleted,
736       auth: null,
737     };
738     WebSocketService.Instance.editComment(deleteForm);
739   }
740
741   handleSaveCommentClick(i: CommentNode) {
742     let saved =
743       i.props.node.comment.saved == undefined
744         ? true
745         : !i.props.node.comment.saved;
746     let form: SaveCommentForm = {
747       comment_id: i.props.node.comment.id,
748       save: saved,
749     };
750
751     WebSocketService.Instance.saveComment(form);
752   }
753
754   handleReplyCancel() {
755     this.state.showReply = false;
756     this.state.showEdit = false;
757     this.setState(this.state);
758   }
759
760   handleCommentUpvote(i: CommentNodeI) {
761     let new_vote = this.state.my_vote == 1 ? 0 : 1;
762
763     if (this.state.my_vote == 1) {
764       this.state.score--;
765       this.state.upvotes--;
766     } else if (this.state.my_vote == -1) {
767       this.state.downvotes--;
768       this.state.upvotes++;
769       this.state.score += 2;
770     } else {
771       this.state.upvotes++;
772       this.state.score++;
773     }
774
775     this.state.my_vote = new_vote;
776
777     let form: CommentLikeForm = {
778       comment_id: i.comment.id,
779       post_id: i.comment.post_id,
780       score: this.state.my_vote,
781     };
782
783     WebSocketService.Instance.likeComment(form);
784     this.setState(this.state);
785   }
786
787   handleCommentDownvote(i: CommentNodeI) {
788     let new_vote = this.state.my_vote == -1 ? 0 : -1;
789
790     if (this.state.my_vote == 1) {
791       this.state.score -= 2;
792       this.state.upvotes--;
793       this.state.downvotes++;
794     } else if (this.state.my_vote == -1) {
795       this.state.downvotes--;
796       this.state.score++;
797     } else {
798       this.state.downvotes++;
799       this.state.score--;
800     }
801
802     this.state.my_vote = new_vote;
803
804     let form: CommentLikeForm = {
805       comment_id: i.comment.id,
806       post_id: i.comment.post_id,
807       score: this.state.my_vote,
808     };
809
810     WebSocketService.Instance.likeComment(form);
811     this.setState(this.state);
812   }
813
814   handleModRemoveShow(i: CommentNode) {
815     i.state.showRemoveDialog = true;
816     i.setState(i.state);
817   }
818
819   handleModRemoveReasonChange(i: CommentNode, event: any) {
820     i.state.removeReason = event.target.value;
821     i.setState(i.state);
822   }
823
824   handleModRemoveSubmit(i: CommentNode) {
825     event.preventDefault();
826     let form: CommentFormI = {
827       content: i.props.node.comment.content,
828       edit_id: i.props.node.comment.id,
829       creator_id: i.props.node.comment.creator_id,
830       post_id: i.props.node.comment.post_id,
831       parent_id: i.props.node.comment.parent_id,
832       removed: !i.props.node.comment.removed,
833       reason: i.state.removeReason,
834       auth: null,
835     };
836     WebSocketService.Instance.editComment(form);
837
838     i.state.showRemoveDialog = false;
839     i.setState(i.state);
840   }
841
842   handleMarkRead(i: CommentNode) {
843     // if it has a user_mention_id field, then its a mention
844     if (i.props.node.comment.user_mention_id) {
845       let form: EditUserMentionForm = {
846         user_mention_id: i.props.node.comment.user_mention_id,
847         read: !i.props.node.comment.read,
848       };
849       WebSocketService.Instance.editUserMention(form);
850     } else {
851       let form: CommentFormI = {
852         content: i.props.node.comment.content,
853         edit_id: i.props.node.comment.id,
854         creator_id: i.props.node.comment.creator_id,
855         post_id: i.props.node.comment.post_id,
856         parent_id: i.props.node.comment.parent_id,
857         read: !i.props.node.comment.read,
858         auth: null,
859       };
860       WebSocketService.Instance.editComment(form);
861     }
862   }
863
864   handleModBanFromCommunityShow(i: CommentNode) {
865     i.state.showBanDialog = !i.state.showBanDialog;
866     i.state.banType = BanType.Community;
867     i.setState(i.state);
868   }
869
870   handleModBanShow(i: CommentNode) {
871     i.state.showBanDialog = !i.state.showBanDialog;
872     i.state.banType = BanType.Site;
873     i.setState(i.state);
874   }
875
876   handleModBanReasonChange(i: CommentNode, event: any) {
877     i.state.banReason = event.target.value;
878     i.setState(i.state);
879   }
880
881   handleModBanExpiresChange(i: CommentNode, event: any) {
882     i.state.banExpires = event.target.value;
883     i.setState(i.state);
884   }
885
886   handleModBanFromCommunitySubmit(i: CommentNode) {
887     i.state.banType = BanType.Community;
888     i.setState(i.state);
889     i.handleModBanBothSubmit(i);
890   }
891
892   handleModBanSubmit(i: CommentNode) {
893     i.state.banType = BanType.Site;
894     i.setState(i.state);
895     i.handleModBanBothSubmit(i);
896   }
897
898   handleModBanBothSubmit(i: CommentNode) {
899     event.preventDefault();
900
901     if (i.state.banType == BanType.Community) {
902       let form: BanFromCommunityForm = {
903         user_id: i.props.node.comment.creator_id,
904         community_id: i.props.node.comment.community_id,
905         ban: !i.props.node.comment.banned_from_community,
906         reason: i.state.banReason,
907         expires: getUnixTime(i.state.banExpires),
908       };
909       WebSocketService.Instance.banFromCommunity(form);
910     } else {
911       let form: BanUserForm = {
912         user_id: i.props.node.comment.creator_id,
913         ban: !i.props.node.comment.banned,
914         reason: i.state.banReason,
915         expires: getUnixTime(i.state.banExpires),
916       };
917       WebSocketService.Instance.banUser(form);
918     }
919
920     i.state.showBanDialog = false;
921     i.setState(i.state);
922   }
923
924   handleShowConfirmAppointAsMod(i: CommentNode) {
925     i.state.showConfirmAppointAsMod = true;
926     i.setState(i.state);
927   }
928
929   handleCancelConfirmAppointAsMod(i: CommentNode) {
930     i.state.showConfirmAppointAsMod = false;
931     i.setState(i.state);
932   }
933
934   handleAddModToCommunity(i: CommentNode) {
935     let form: AddModToCommunityForm = {
936       user_id: i.props.node.comment.creator_id,
937       community_id: i.props.node.comment.community_id,
938       added: !i.isMod,
939     };
940     WebSocketService.Instance.addModToCommunity(form);
941     i.state.showConfirmAppointAsMod = false;
942     i.setState(i.state);
943   }
944
945   handleShowConfirmAppointAsAdmin(i: CommentNode) {
946     i.state.showConfirmAppointAsAdmin = true;
947     i.setState(i.state);
948   }
949
950   handleCancelConfirmAppointAsAdmin(i: CommentNode) {
951     i.state.showConfirmAppointAsAdmin = false;
952     i.setState(i.state);
953   }
954
955   handleAddAdmin(i: CommentNode) {
956     let form: AddAdminForm = {
957       user_id: i.props.node.comment.creator_id,
958       added: !i.isAdmin,
959     };
960     WebSocketService.Instance.addAdmin(form);
961     i.state.showConfirmAppointAsAdmin = false;
962     i.setState(i.state);
963   }
964
965   handleShowConfirmTransferCommunity(i: CommentNode) {
966     i.state.showConfirmTransferCommunity = true;
967     i.setState(i.state);
968   }
969
970   handleCancelShowConfirmTransferCommunity(i: CommentNode) {
971     i.state.showConfirmTransferCommunity = false;
972     i.setState(i.state);
973   }
974
975   handleTransferCommunity(i: CommentNode) {
976     let form: TransferCommunityForm = {
977       community_id: i.props.node.comment.community_id,
978       user_id: i.props.node.comment.creator_id,
979     };
980     WebSocketService.Instance.transferCommunity(form);
981     i.state.showConfirmTransferCommunity = false;
982     i.setState(i.state);
983   }
984
985   handleShowConfirmTransferSite(i: CommentNode) {
986     i.state.showConfirmTransferSite = true;
987     i.setState(i.state);
988   }
989
990   handleCancelShowConfirmTransferSite(i: CommentNode) {
991     i.state.showConfirmTransferSite = false;
992     i.setState(i.state);
993   }
994
995   handleTransferSite(i: CommentNode) {
996     let form: TransferSiteForm = {
997       user_id: i.props.node.comment.creator_id,
998     };
999     WebSocketService.Instance.transferSite(form);
1000     i.state.showConfirmTransferSite = false;
1001     i.setState(i.state);
1002   }
1003
1004   get isCommentNew(): boolean {
1005     let now = moment.utc().subtract(10, 'minutes');
1006     let then = moment.utc(this.props.node.comment.published);
1007     return now.isBefore(then);
1008   }
1009
1010   handleCommentCollapse(i: CommentNode) {
1011     i.state.collapsed = !i.state.collapsed;
1012     i.setState(i.state);
1013   }
1014
1015   handleViewSource(i: CommentNode) {
1016     i.state.viewSource = !i.state.viewSource;
1017     i.setState(i.state);
1018   }
1019 }