]> Untitled Git - lemmy.git/blob - ui/src/components/comment-node.tsx
Merge remote-tracking branch 'upstream/master'
[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 } from '../interfaces';
19 import { WebSocketService, UserService } from '../services';
20 import {
21   mdToHtml,
22   getUnixTime,
23   canMod,
24   isMod,
25   pictshareAvatarThumbnail,
26   showAvatars,
27 } from '../utils';
28 import moment from 'moment';
29 import { MomentTime } from './moment-time';
30 import { CommentForm } from './comment-form';
31 import { CommentNodes } from './comment-nodes';
32 import { i18n } from '../i18next';
33
34 interface CommentNodeState {
35   showReply: boolean;
36   showEdit: boolean;
37   showRemoveDialog: boolean;
38   removeReason: string;
39   showBanDialog: boolean;
40   banReason: string;
41   banExpires: string;
42   banType: BanType;
43   showConfirmTransferSite: boolean;
44   showConfirmTransferCommunity: boolean;
45   showConfirmAppointAsMod: boolean;
46   showConfirmAppointAsAdmin: boolean;
47   collapsed: boolean;
48   viewSource: boolean;
49   upvoteLoading: boolean;
50   downvoteLoading: boolean;
51 }
52
53 interface CommentNodeProps {
54   node: CommentNodeI;
55   noIndent?: boolean;
56   viewOnly?: boolean;
57   locked?: boolean;
58   markable?: boolean;
59   moderators: Array<CommunityUser>;
60   admins: Array<UserView>;
61   postCreatorId?: number;
62 }
63
64 export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
65   private emptyState: CommentNodeState = {
66     showReply: false,
67     showEdit: false,
68     showRemoveDialog: false,
69     removeReason: null,
70     showBanDialog: false,
71     banReason: null,
72     banExpires: null,
73     banType: BanType.Community,
74     collapsed: false,
75     viewSource: false,
76     showConfirmTransferSite: false,
77     showConfirmTransferCommunity: false,
78     showConfirmAppointAsMod: false,
79     showConfirmAppointAsAdmin: false,
80     upvoteLoading: this.props.node.comment.upvoteLoading,
81     downvoteLoading: this.props.node.comment.downvoteLoading,
82   };
83
84   constructor(props: any, context: any) {
85     super(props, context);
86
87     this.state = this.emptyState;
88     this.handleReplyCancel = this.handleReplyCancel.bind(this);
89     this.handleCommentUpvote = this.handleCommentUpvote.bind(this);
90     this.handleCommentDownvote = this.handleCommentDownvote.bind(this);
91   }
92
93   componentWillReceiveProps(nextProps: CommentNodeProps) {
94     if (
95       nextProps.node.comment.upvoteLoading !== this.state.upvoteLoading ||
96       nextProps.node.comment.downvoteLoading !== this.state.downvoteLoading
97     ) {
98       this.setState({
99         upvoteLoading: false,
100         downvoteLoading: false,
101       });
102     }
103   }
104
105   render() {
106     let node = this.props.node;
107     return (
108       <div
109         className={`comment ${
110           node.comment.parent_id && !this.props.noIndent ? 'ml-4' : ''
111         }`}
112       >
113         {!this.state.collapsed && (
114           <div
115             className={`vote-bar mr-2 float-left small text-center ${this.props
116               .viewOnly && 'no-click'}`}
117           >
118             <button
119               className={`btn btn-link p-0 ${
120                 node.comment.my_vote == 1 ? 'text-info' : 'text-muted'
121               }`}
122               onClick={linkEvent(node, this.handleCommentUpvote)}
123             >
124               {this.state.upvoteLoading ? (
125                 <svg class="icon icon-spinner spin upvote">
126                   <use xlinkHref="#icon-spinner"></use>
127                 </svg>
128               ) : (
129                 <svg class="icon upvote">
130                   <use xlinkHref="#icon-arrow-up"></use>
131                 </svg>
132               )}
133             </button>
134             <div class={`font-weight-bold text-muted`}>
135               {node.comment.score}
136             </div>
137             {WebSocketService.Instance.site.enable_downvotes && (
138               <button
139                 className={`btn btn-link p-0 ${
140                   node.comment.my_vote == -1 ? 'text-danger' : 'text-muted'
141                 }`}
142                 onClick={linkEvent(node, this.handleCommentDownvote)}
143               >
144                 {this.state.downvoteLoading ? (
145                   <svg class="icon icon-spinner spin downvote">
146                     <use xlinkHref="#icon-spinner"></use>
147                   </svg>
148                 ) : (
149                   <svg class="icon downvote">
150                     <use xlinkHref="#icon-arrow-down"></use>
151                   </svg>
152                 )}
153               </button>
154             )}
155           </div>
156         )}
157         <div
158           id={`comment-${node.comment.id}`}
159           className={`details comment-node ml-4 ${
160             this.isCommentNew ? 'mark' : ''
161           }`}
162         >
163           <ul class="list-inline mb-0 text-muted small">
164             <li className="list-inline-item">
165               <Link
166                 className="text-info"
167                 to={`/u/${node.comment.creator_name}`}
168               >
169                 {node.comment.creator_avatar && showAvatars() && (
170                   <img
171                     height="32"
172                     width="32"
173                     src={pictshareAvatarThumbnail(node.comment.creator_avatar)}
174                     class="rounded-circle mr-1"
175                   />
176                 )}
177                 <span>{node.comment.creator_name}</span>
178               </Link>
179             </li>
180             {this.isMod && (
181               <li className="list-inline-item badge badge-light">
182                 {i18n.t('mod')}
183               </li>
184             )}
185             {this.isAdmin && (
186               <li className="list-inline-item badge badge-light">
187                 {i18n.t('admin')}
188               </li>
189             )}
190             {this.isPostCreator && (
191               <li className="list-inline-item badge badge-light">
192                 {i18n.t('creator')}
193               </li>
194             )}
195             {(node.comment.banned_from_community || node.comment.banned) && (
196               <li className="list-inline-item badge badge-danger">
197                 {i18n.t('banned')}
198               </li>
199             )}
200             <li className="list-inline-item">
201               <span>
202                 (<span className="text-info">+{node.comment.upvotes}</span>
203                 <span> | </span>
204                 <span className="text-danger">-{node.comment.downvotes}</span>
205                 <span>) </span>
206               </span>
207             </li>
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           />
624         )}
625         {/* A collapsed clearfix */}
626         {this.state.collapsed && <div class="row col-12"></div>}
627       </div>
628     );
629   }
630
631   get myComment(): boolean {
632     return (
633       UserService.Instance.user &&
634       this.props.node.comment.creator_id == UserService.Instance.user.id
635     );
636   }
637
638   get isMod(): boolean {
639     return (
640       this.props.moderators &&
641       isMod(
642         this.props.moderators.map(m => m.user_id),
643         this.props.node.comment.creator_id
644       )
645     );
646   }
647
648   get isAdmin(): boolean {
649     return (
650       this.props.admins &&
651       isMod(
652         this.props.admins.map(a => a.id),
653         this.props.node.comment.creator_id
654       )
655     );
656   }
657
658   get isPostCreator(): boolean {
659     return this.props.node.comment.creator_id == this.props.postCreatorId;
660   }
661
662   get canMod(): boolean {
663     if (this.props.admins && this.props.moderators) {
664       let adminsThenMods = this.props.admins
665         .map(a => a.id)
666         .concat(this.props.moderators.map(m => m.user_id));
667
668       return canMod(
669         UserService.Instance.user,
670         adminsThenMods,
671         this.props.node.comment.creator_id
672       );
673     } else {
674       return false;
675     }
676   }
677
678   get canAdmin(): boolean {
679     return (
680       this.props.admins &&
681       canMod(
682         UserService.Instance.user,
683         this.props.admins.map(a => a.id),
684         this.props.node.comment.creator_id
685       )
686     );
687   }
688
689   get amCommunityCreator(): boolean {
690     return (
691       this.props.moderators &&
692       UserService.Instance.user &&
693       this.props.node.comment.creator_id != UserService.Instance.user.id &&
694       UserService.Instance.user.id == this.props.moderators[0].user_id
695     );
696   }
697
698   get amSiteCreator(): boolean {
699     return (
700       this.props.admins &&
701       UserService.Instance.user &&
702       this.props.node.comment.creator_id != UserService.Instance.user.id &&
703       UserService.Instance.user.id == this.props.admins[0].id
704     );
705   }
706
707   get commentUnlessRemoved(): string {
708     let node = this.props.node;
709     return node.comment.removed
710       ? `*${i18n.t('removed')}*`
711       : node.comment.deleted
712       ? `*${i18n.t('deleted')}*`
713       : node.comment.content;
714   }
715
716   handleReplyClick(i: CommentNode) {
717     i.state.showReply = true;
718     i.setState(i.state);
719   }
720
721   handleEditClick(i: CommentNode) {
722     i.state.showEdit = true;
723     i.setState(i.state);
724   }
725
726   handleDeleteClick(i: CommentNode) {
727     let deleteForm: CommentFormI = {
728       content: i.props.node.comment.content,
729       edit_id: i.props.node.comment.id,
730       creator_id: i.props.node.comment.creator_id,
731       post_id: i.props.node.comment.post_id,
732       parent_id: i.props.node.comment.parent_id,
733       deleted: !i.props.node.comment.deleted,
734       auth: null,
735     };
736     WebSocketService.Instance.editComment(deleteForm);
737   }
738
739   handleSaveCommentClick(i: CommentNode) {
740     let saved =
741       i.props.node.comment.saved == undefined
742         ? true
743         : !i.props.node.comment.saved;
744     let form: SaveCommentForm = {
745       comment_id: i.props.node.comment.id,
746       save: saved,
747     };
748
749     WebSocketService.Instance.saveComment(form);
750   }
751
752   handleReplyCancel() {
753     this.state.showReply = false;
754     this.state.showEdit = false;
755     this.setState(this.state);
756   }
757
758   handleCommentUpvote(i: CommentNodeI) {
759     if (UserService.Instance.user) {
760       this.setState({
761         upvoteLoading: true,
762       });
763     }
764     let form: CommentLikeForm = {
765       comment_id: i.comment.id,
766       post_id: i.comment.post_id,
767       score: i.comment.my_vote == 1 ? 0 : 1,
768     };
769     WebSocketService.Instance.likeComment(form);
770   }
771
772   handleCommentDownvote(i: CommentNodeI) {
773     if (UserService.Instance.user) {
774       this.setState({
775         downvoteLoading: true,
776       });
777     }
778     let form: CommentLikeForm = {
779       comment_id: i.comment.id,
780       post_id: i.comment.post_id,
781       score: i.comment.my_vote == -1 ? 0 : -1,
782     };
783     WebSocketService.Instance.likeComment(form);
784   }
785
786   handleModRemoveShow(i: CommentNode) {
787     i.state.showRemoveDialog = true;
788     i.setState(i.state);
789   }
790
791   handleModRemoveReasonChange(i: CommentNode, event: any) {
792     i.state.removeReason = event.target.value;
793     i.setState(i.state);
794   }
795
796   handleModRemoveSubmit(i: CommentNode) {
797     event.preventDefault();
798     let form: CommentFormI = {
799       content: i.props.node.comment.content,
800       edit_id: i.props.node.comment.id,
801       creator_id: i.props.node.comment.creator_id,
802       post_id: i.props.node.comment.post_id,
803       parent_id: i.props.node.comment.parent_id,
804       removed: !i.props.node.comment.removed,
805       reason: i.state.removeReason,
806       auth: null,
807     };
808     WebSocketService.Instance.editComment(form);
809
810     i.state.showRemoveDialog = false;
811     i.setState(i.state);
812   }
813
814   handleMarkRead(i: CommentNode) {
815     // if it has a user_mention_id field, then its a mention
816     if (i.props.node.comment.user_mention_id) {
817       let form: EditUserMentionForm = {
818         user_mention_id: i.props.node.comment.user_mention_id,
819         read: !i.props.node.comment.read,
820       };
821       WebSocketService.Instance.editUserMention(form);
822     } else {
823       let form: CommentFormI = {
824         content: i.props.node.comment.content,
825         edit_id: i.props.node.comment.id,
826         creator_id: i.props.node.comment.creator_id,
827         post_id: i.props.node.comment.post_id,
828         parent_id: i.props.node.comment.parent_id,
829         read: !i.props.node.comment.read,
830         auth: null,
831       };
832       WebSocketService.Instance.editComment(form);
833     }
834   }
835
836   handleModBanFromCommunityShow(i: CommentNode) {
837     i.state.showBanDialog = !i.state.showBanDialog;
838     i.state.banType = BanType.Community;
839     i.setState(i.state);
840   }
841
842   handleModBanShow(i: CommentNode) {
843     i.state.showBanDialog = !i.state.showBanDialog;
844     i.state.banType = BanType.Site;
845     i.setState(i.state);
846   }
847
848   handleModBanReasonChange(i: CommentNode, event: any) {
849     i.state.banReason = event.target.value;
850     i.setState(i.state);
851   }
852
853   handleModBanExpiresChange(i: CommentNode, event: any) {
854     i.state.banExpires = event.target.value;
855     i.setState(i.state);
856   }
857
858   handleModBanFromCommunitySubmit(i: CommentNode) {
859     i.state.banType = BanType.Community;
860     i.setState(i.state);
861     i.handleModBanBothSubmit(i);
862   }
863
864   handleModBanSubmit(i: CommentNode) {
865     i.state.banType = BanType.Site;
866     i.setState(i.state);
867     i.handleModBanBothSubmit(i);
868   }
869
870   handleModBanBothSubmit(i: CommentNode) {
871     event.preventDefault();
872
873     if (i.state.banType == BanType.Community) {
874       let form: BanFromCommunityForm = {
875         user_id: i.props.node.comment.creator_id,
876         community_id: i.props.node.comment.community_id,
877         ban: !i.props.node.comment.banned_from_community,
878         reason: i.state.banReason,
879         expires: getUnixTime(i.state.banExpires),
880       };
881       WebSocketService.Instance.banFromCommunity(form);
882     } else {
883       let form: BanUserForm = {
884         user_id: i.props.node.comment.creator_id,
885         ban: !i.props.node.comment.banned,
886         reason: i.state.banReason,
887         expires: getUnixTime(i.state.banExpires),
888       };
889       WebSocketService.Instance.banUser(form);
890     }
891
892     i.state.showBanDialog = false;
893     i.setState(i.state);
894   }
895
896   handleShowConfirmAppointAsMod(i: CommentNode) {
897     i.state.showConfirmAppointAsMod = true;
898     i.setState(i.state);
899   }
900
901   handleCancelConfirmAppointAsMod(i: CommentNode) {
902     i.state.showConfirmAppointAsMod = false;
903     i.setState(i.state);
904   }
905
906   handleAddModToCommunity(i: CommentNode) {
907     let form: AddModToCommunityForm = {
908       user_id: i.props.node.comment.creator_id,
909       community_id: i.props.node.comment.community_id,
910       added: !i.isMod,
911     };
912     WebSocketService.Instance.addModToCommunity(form);
913     i.state.showConfirmAppointAsMod = false;
914     i.setState(i.state);
915   }
916
917   handleShowConfirmAppointAsAdmin(i: CommentNode) {
918     i.state.showConfirmAppointAsAdmin = true;
919     i.setState(i.state);
920   }
921
922   handleCancelConfirmAppointAsAdmin(i: CommentNode) {
923     i.state.showConfirmAppointAsAdmin = false;
924     i.setState(i.state);
925   }
926
927   handleAddAdmin(i: CommentNode) {
928     let form: AddAdminForm = {
929       user_id: i.props.node.comment.creator_id,
930       added: !i.isAdmin,
931     };
932     WebSocketService.Instance.addAdmin(form);
933     i.state.showConfirmAppointAsAdmin = false;
934     i.setState(i.state);
935   }
936
937   handleShowConfirmTransferCommunity(i: CommentNode) {
938     i.state.showConfirmTransferCommunity = true;
939     i.setState(i.state);
940   }
941
942   handleCancelShowConfirmTransferCommunity(i: CommentNode) {
943     i.state.showConfirmTransferCommunity = false;
944     i.setState(i.state);
945   }
946
947   handleTransferCommunity(i: CommentNode) {
948     let form: TransferCommunityForm = {
949       community_id: i.props.node.comment.community_id,
950       user_id: i.props.node.comment.creator_id,
951     };
952     WebSocketService.Instance.transferCommunity(form);
953     i.state.showConfirmTransferCommunity = false;
954     i.setState(i.state);
955   }
956
957   handleShowConfirmTransferSite(i: CommentNode) {
958     i.state.showConfirmTransferSite = true;
959     i.setState(i.state);
960   }
961
962   handleCancelShowConfirmTransferSite(i: CommentNode) {
963     i.state.showConfirmTransferSite = false;
964     i.setState(i.state);
965   }
966
967   handleTransferSite(i: CommentNode) {
968     let form: TransferSiteForm = {
969       user_id: i.props.node.comment.creator_id,
970     };
971     WebSocketService.Instance.transferSite(form);
972     i.state.showConfirmTransferSite = false;
973     i.setState(i.state);
974   }
975
976   get isCommentNew(): boolean {
977     let now = moment.utc().subtract(10, 'minutes');
978     let then = moment.utc(this.props.node.comment.published);
979     return now.isBefore(then);
980   }
981
982   handleCommentCollapse(i: CommentNode) {
983     i.state.collapsed = !i.state.collapsed;
984     i.setState(i.state);
985   }
986
987   handleViewSource(i: CommentNode) {
988     i.state.viewSource = !i.state.viewSource;
989     i.setState(i.state);
990   }
991 }