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