]> Untitled Git - lemmy.git/blob - ui/src/components/post-listing.tsx
Merge branch 'dev' into federation
[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 img-expanded" 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">
336                   {i18n.t('cross_posted_to')}
337                 </li>
338                 {this.props.post.duplicates.map(post => (
339                   <li className="list-inline-item mr-2">
340                     <Link to={`/post/${post.id}`}>{post.community_name}</Link>
341                   </li>
342                 ))}
343               </>
344             )}
345           </ul>
346           <ul class="list-inline mb-1 text-muted small font-weight-bold">
347             {UserService.Instance.user && (
348               <>
349                 {this.props.showBody && (
350                   <>
351                     <li className="list-inline-item mr-2">
352                       <span
353                         class="pointer"
354                         onClick={linkEvent(this, this.handleSavePostClick)}
355                       >
356                         {post.saved ? i18n.t('unsave') : i18n.t('save')}
357                       </span>
358                     </li>
359                     <li className="list-inline-item mr-2">
360                       <Link
361                         className="text-muted"
362                         to={`/create_post${this.crossPostParams}`}
363                       >
364                         {i18n.t('cross_post')}
365                       </Link>
366                     </li>
367                   </>
368                 )}
369                 {this.myPost && this.props.showBody && (
370                   <>
371                     <li className="list-inline-item">
372                       <span
373                         class="pointer"
374                         onClick={linkEvent(this, this.handleEditClick)}
375                       >
376                         {i18n.t('edit')}
377                       </span>
378                     </li>
379                     <li className="list-inline-item mr-2">
380                       <span
381                         class="pointer"
382                         onClick={linkEvent(this, this.handleDeleteClick)}
383                       >
384                         {!post.deleted ? i18n.t('delete') : i18n.t('restore')}
385                       </span>
386                     </li>
387                   </>
388                 )}
389                 {this.canModOnSelf && (
390                   <>
391                     <li className="list-inline-item">
392                       <span
393                         class="pointer"
394                         onClick={linkEvent(this, this.handleModLock)}
395                       >
396                         {post.locked ? i18n.t('unlock') : i18n.t('lock')}
397                       </span>
398                     </li>
399                     <li className="list-inline-item">
400                       <span
401                         class="pointer"
402                         onClick={linkEvent(this, this.handleModSticky)}
403                       >
404                         {post.stickied ? i18n.t('unsticky') : i18n.t('sticky')}
405                       </span>
406                     </li>
407                   </>
408                 )}
409                 {/* Mods can ban from community, and appoint as mods to community */}
410                 {(this.canMod || this.canAdmin) && (
411                   <li className="list-inline-item">
412                     {!post.removed ? (
413                       <span
414                         class="pointer"
415                         onClick={linkEvent(this, this.handleModRemoveShow)}
416                       >
417                         {i18n.t('remove')}
418                       </span>
419                     ) : (
420                       <span
421                         class="pointer"
422                         onClick={linkEvent(this, this.handleModRemoveSubmit)}
423                       >
424                         {i18n.t('restore')}
425                       </span>
426                     )}
427                   </li>
428                 )}
429                 {this.canMod && (
430                   <>
431                     {!this.isMod && (
432                       <li className="list-inline-item">
433                         {!post.banned_from_community ? (
434                           <span
435                             class="pointer"
436                             onClick={linkEvent(
437                               this,
438                               this.handleModBanFromCommunityShow
439                             )}
440                           >
441                             {i18n.t('ban')}
442                           </span>
443                         ) : (
444                           <span
445                             class="pointer"
446                             onClick={linkEvent(
447                               this,
448                               this.handleModBanFromCommunitySubmit
449                             )}
450                           >
451                             {i18n.t('unban')}
452                           </span>
453                         )}
454                       </li>
455                     )}
456                     {!post.banned_from_community && (
457                       <li className="list-inline-item">
458                         <span
459                           class="pointer"
460                           onClick={linkEvent(
461                             this,
462                             this.handleAddModToCommunity
463                           )}
464                         >
465                           {this.isMod
466                             ? i18n.t('remove_as_mod')
467                             : i18n.t('appoint_as_mod')}
468                         </span>
469                       </li>
470                     )}
471                   </>
472                 )}
473                 {/* Community creators and admins can transfer community to another mod */}
474                 {(this.amCommunityCreator || this.canAdmin) && this.isMod && (
475                   <li className="list-inline-item">
476                     {!this.state.showConfirmTransferCommunity ? (
477                       <span
478                         class="pointer"
479                         onClick={linkEvent(
480                           this,
481                           this.handleShowConfirmTransferCommunity
482                         )}
483                       >
484                         {i18n.t('transfer_community')}
485                       </span>
486                     ) : (
487                       <>
488                         <span class="d-inline-block mr-1">
489                           {i18n.t('are_you_sure')}
490                         </span>
491                         <span
492                           class="pointer d-inline-block mr-1"
493                           onClick={linkEvent(
494                             this,
495                             this.handleTransferCommunity
496                           )}
497                         >
498                           {i18n.t('yes')}
499                         </span>
500                         <span
501                           class="pointer d-inline-block"
502                           onClick={linkEvent(
503                             this,
504                             this.handleCancelShowConfirmTransferCommunity
505                           )}
506                         >
507                           {i18n.t('no')}
508                         </span>
509                       </>
510                     )}
511                   </li>
512                 )}
513                 {/* Admins can ban from all, and appoint other admins */}
514                 {this.canAdmin && (
515                   <>
516                     {!this.isAdmin && (
517                       <li className="list-inline-item">
518                         {!post.banned ? (
519                           <span
520                             class="pointer"
521                             onClick={linkEvent(this, this.handleModBanShow)}
522                           >
523                             {i18n.t('ban_from_site')}
524                           </span>
525                         ) : (
526                           <span
527                             class="pointer"
528                             onClick={linkEvent(this, this.handleModBanSubmit)}
529                           >
530                             {i18n.t('unban_from_site')}
531                           </span>
532                         )}
533                       </li>
534                     )}
535                     {!post.banned && (
536                       <li className="list-inline-item">
537                         <span
538                           class="pointer"
539                           onClick={linkEvent(this, this.handleAddAdmin)}
540                         >
541                           {this.isAdmin
542                             ? i18n.t('remove_as_admin')
543                             : i18n.t('appoint_as_admin')}
544                         </span>
545                       </li>
546                     )}
547                   </>
548                 )}
549                 {/* Site Creator can transfer to another admin */}
550                 {this.amSiteCreator && this.isAdmin && (
551                   <li className="list-inline-item">
552                     {!this.state.showConfirmTransferSite ? (
553                       <span
554                         class="pointer"
555                         onClick={linkEvent(
556                           this,
557                           this.handleShowConfirmTransferSite
558                         )}
559                       >
560                         {i18n.t('transfer_site')}
561                       </span>
562                     ) : (
563                       <>
564                         <span class="d-inline-block mr-1">
565                           {i18n.t('are_you_sure')}
566                         </span>
567                         <span
568                           class="pointer d-inline-block mr-1"
569                           onClick={linkEvent(this, this.handleTransferSite)}
570                         >
571                           {i18n.t('yes')}
572                         </span>
573                         <span
574                           class="pointer d-inline-block"
575                           onClick={linkEvent(
576                             this,
577                             this.handleCancelShowConfirmTransferSite
578                           )}
579                         >
580                           {i18n.t('no')}
581                         </span>
582                       </>
583                     )}
584                   </li>
585                 )}
586               </>
587             )}
588             {this.props.showBody && post.body && (
589               <li className="list-inline-item">
590                 <span
591                   className="pointer"
592                   onClick={linkEvent(this, this.handleViewSource)}
593                 >
594                   {i18n.t('view_source')}
595                 </span>
596               </li>
597             )}
598           </ul>
599           {this.state.showRemoveDialog && (
600             <form
601               class="form-inline"
602               onSubmit={linkEvent(this, this.handleModRemoveSubmit)}
603             >
604               <input
605                 type="text"
606                 class="form-control mr-2"
607                 placeholder={i18n.t('reason')}
608                 value={this.state.removeReason}
609                 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
610               />
611               <button type="submit" class="btn btn-secondary">
612                 {i18n.t('remove_post')}
613               </button>
614             </form>
615           )}
616           {this.state.showBanDialog && (
617             <form onSubmit={linkEvent(this, this.handleModBanBothSubmit)}>
618               <div class="form-group row">
619                 <label class="col-form-label" htmlFor="post-listing-reason">
620                   {i18n.t('reason')}
621                 </label>
622                 <input
623                   type="text"
624                   id="post-listing-reason"
625                   class="form-control mr-2"
626                   placeholder={i18n.t('reason')}
627                   value={this.state.banReason}
628                   onInput={linkEvent(this, this.handleModBanReasonChange)}
629                 />
630               </div>
631               {/* TODO hold off on expires until later */}
632               {/* <div class="form-group row"> */}
633               {/*   <label class="col-form-label">Expires</label> */}
634               {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.banExpires} onInput={linkEvent(this, this.handleModBanExpiresChange)} /> */}
635               {/* </div> */}
636               <div class="form-group row">
637                 <button type="submit" class="btn btn-secondary">
638                   {i18n.t('ban')} {post.creator_name}
639                 </button>
640               </div>
641             </form>
642           )}
643           {this.props.showBody && post.body && (
644             <>
645               {this.state.viewSource ? (
646                 <pre>{post.body}</pre>
647               ) : (
648                 <div
649                   className="md-div"
650                   dangerouslySetInnerHTML={mdToHtml(post.body)}
651                 />
652               )}
653             </>
654           )}
655         </div>
656       </div>
657     );
658   }
659
660   private get myPost(): boolean {
661     return (
662       UserService.Instance.user &&
663       this.props.post.creator_id == UserService.Instance.user.id
664     );
665   }
666
667   get isMod(): boolean {
668     return (
669       this.props.moderators &&
670       isMod(
671         this.props.moderators.map(m => m.user_id),
672         this.props.post.creator_id
673       )
674     );
675   }
676
677   get isAdmin(): boolean {
678     return (
679       this.props.admins &&
680       isMod(
681         this.props.admins.map(a => a.id),
682         this.props.post.creator_id
683       )
684     );
685   }
686
687   get canMod(): boolean {
688     if (this.props.admins && this.props.moderators) {
689       let adminsThenMods = this.props.admins
690         .map(a => a.id)
691         .concat(this.props.moderators.map(m => m.user_id));
692
693       return canMod(
694         UserService.Instance.user,
695         adminsThenMods,
696         this.props.post.creator_id
697       );
698     } else {
699       return false;
700     }
701   }
702
703   get canModOnSelf(): boolean {
704     if (this.props.admins && this.props.moderators) {
705       let adminsThenMods = this.props.admins
706         .map(a => a.id)
707         .concat(this.props.moderators.map(m => m.user_id));
708
709       return canMod(
710         UserService.Instance.user,
711         adminsThenMods,
712         this.props.post.creator_id,
713         true
714       );
715     } else {
716       return false;
717     }
718   }
719
720   get canAdmin(): boolean {
721     return (
722       this.props.admins &&
723       canMod(
724         UserService.Instance.user,
725         this.props.admins.map(a => a.id),
726         this.props.post.creator_id
727       )
728     );
729   }
730
731   get amCommunityCreator(): boolean {
732     return (
733       this.props.moderators &&
734       UserService.Instance.user &&
735       this.props.post.creator_id != UserService.Instance.user.id &&
736       UserService.Instance.user.id == this.props.moderators[0].user_id
737     );
738   }
739
740   get amSiteCreator(): boolean {
741     return (
742       this.props.admins &&
743       UserService.Instance.user &&
744       this.props.post.creator_id != UserService.Instance.user.id &&
745       UserService.Instance.user.id == this.props.admins[0].id
746     );
747   }
748
749   handlePostLike(i: PostListing) {
750     if (UserService.Instance.user) {
751       i.setState({ upvoteLoading: true });
752     }
753
754     let form: CreatePostLikeForm = {
755       post_id: i.props.post.id,
756       score: i.props.post.my_vote == 1 ? 0 : 1,
757     };
758
759     WebSocketService.Instance.likePost(form);
760   }
761
762   handlePostDisLike(i: PostListing) {
763     if (UserService.Instance.user) {
764       i.setState({ downvoteLoading: true });
765     }
766
767     let form: CreatePostLikeForm = {
768       post_id: i.props.post.id,
769       score: i.props.post.my_vote == -1 ? 0 : -1,
770     };
771     WebSocketService.Instance.likePost(form);
772   }
773
774   handleEditClick(i: PostListing) {
775     i.state.showEdit = true;
776     i.setState(i.state);
777   }
778
779   handleEditCancel() {
780     this.state.showEdit = false;
781     this.setState(this.state);
782   }
783
784   // The actual editing is done in the recieve for post
785   handleEditPost() {
786     this.state.showEdit = false;
787     this.setState(this.state);
788   }
789
790   handleDeleteClick(i: PostListing) {
791     let deleteForm: PostFormI = {
792       body: i.props.post.body,
793       community_id: i.props.post.community_id,
794       name: i.props.post.name,
795       url: i.props.post.url,
796       edit_id: i.props.post.id,
797       creator_id: i.props.post.creator_id,
798       deleted: !i.props.post.deleted,
799       nsfw: i.props.post.nsfw,
800       auth: null,
801     };
802     WebSocketService.Instance.editPost(deleteForm);
803   }
804
805   handleSavePostClick(i: PostListing) {
806     let saved = i.props.post.saved == undefined ? true : !i.props.post.saved;
807     let form: SavePostForm = {
808       post_id: i.props.post.id,
809       save: saved,
810     };
811
812     WebSocketService.Instance.savePost(form);
813   }
814
815   get crossPostParams(): string {
816     let params = `?title=${this.props.post.name}`;
817     if (this.props.post.url) {
818       params += `&url=${this.props.post.url}`;
819     }
820     if (this.props.post.body) {
821       params += `&body=${this.props.post.body}`;
822     }
823     return params;
824   }
825
826   handleModRemoveShow(i: PostListing) {
827     i.state.showRemoveDialog = true;
828     i.setState(i.state);
829   }
830
831   handleModRemoveReasonChange(i: PostListing, event: any) {
832     i.state.removeReason = event.target.value;
833     i.setState(i.state);
834   }
835
836   handleModRemoveSubmit(i: PostListing) {
837     event.preventDefault();
838     let form: PostFormI = {
839       name: i.props.post.name,
840       community_id: i.props.post.community_id,
841       edit_id: i.props.post.id,
842       creator_id: i.props.post.creator_id,
843       removed: !i.props.post.removed,
844       reason: i.state.removeReason,
845       nsfw: i.props.post.nsfw,
846       auth: null,
847     };
848     WebSocketService.Instance.editPost(form);
849
850     i.state.showRemoveDialog = false;
851     i.setState(i.state);
852   }
853
854   handleModLock(i: PostListing) {
855     let form: PostFormI = {
856       name: i.props.post.name,
857       community_id: i.props.post.community_id,
858       edit_id: i.props.post.id,
859       creator_id: i.props.post.creator_id,
860       nsfw: i.props.post.nsfw,
861       locked: !i.props.post.locked,
862       auth: null,
863     };
864     WebSocketService.Instance.editPost(form);
865   }
866
867   handleModSticky(i: PostListing) {
868     let form: PostFormI = {
869       name: i.props.post.name,
870       community_id: i.props.post.community_id,
871       edit_id: i.props.post.id,
872       creator_id: i.props.post.creator_id,
873       nsfw: i.props.post.nsfw,
874       stickied: !i.props.post.stickied,
875       auth: null,
876     };
877     WebSocketService.Instance.editPost(form);
878   }
879
880   handleModBanFromCommunityShow(i: PostListing) {
881     i.state.showBanDialog = true;
882     i.state.banType = BanType.Community;
883     i.setState(i.state);
884   }
885
886   handleModBanShow(i: PostListing) {
887     i.state.showBanDialog = true;
888     i.state.banType = BanType.Site;
889     i.setState(i.state);
890   }
891
892   handleModBanReasonChange(i: PostListing, event: any) {
893     i.state.banReason = event.target.value;
894     i.setState(i.state);
895   }
896
897   handleModBanExpiresChange(i: PostListing, event: any) {
898     i.state.banExpires = event.target.value;
899     i.setState(i.state);
900   }
901
902   handleModBanFromCommunitySubmit(i: PostListing) {
903     i.state.banType = BanType.Community;
904     i.setState(i.state);
905     i.handleModBanBothSubmit(i);
906   }
907
908   handleModBanSubmit(i: PostListing) {
909     i.state.banType = BanType.Site;
910     i.setState(i.state);
911     i.handleModBanBothSubmit(i);
912   }
913
914   handleModBanBothSubmit(i: PostListing) {
915     event.preventDefault();
916
917     if (i.state.banType == BanType.Community) {
918       let form: BanFromCommunityForm = {
919         user_id: i.props.post.creator_id,
920         community_id: i.props.post.community_id,
921         ban: !i.props.post.banned_from_community,
922         reason: i.state.banReason,
923         expires: getUnixTime(i.state.banExpires),
924       };
925       WebSocketService.Instance.banFromCommunity(form);
926     } else {
927       let form: BanUserForm = {
928         user_id: i.props.post.creator_id,
929         ban: !i.props.post.banned,
930         reason: i.state.banReason,
931         expires: getUnixTime(i.state.banExpires),
932       };
933       WebSocketService.Instance.banUser(form);
934     }
935
936     i.state.showBanDialog = false;
937     i.setState(i.state);
938   }
939
940   handleAddModToCommunity(i: PostListing) {
941     let form: AddModToCommunityForm = {
942       user_id: i.props.post.creator_id,
943       community_id: i.props.post.community_id,
944       added: !i.isMod,
945     };
946     WebSocketService.Instance.addModToCommunity(form);
947     i.setState(i.state);
948   }
949
950   handleAddAdmin(i: PostListing) {
951     let form: AddAdminForm = {
952       user_id: i.props.post.creator_id,
953       added: !i.isAdmin,
954     };
955     WebSocketService.Instance.addAdmin(form);
956     i.setState(i.state);
957   }
958
959   handleShowConfirmTransferCommunity(i: PostListing) {
960     i.state.showConfirmTransferCommunity = true;
961     i.setState(i.state);
962   }
963
964   handleCancelShowConfirmTransferCommunity(i: PostListing) {
965     i.state.showConfirmTransferCommunity = false;
966     i.setState(i.state);
967   }
968
969   handleTransferCommunity(i: PostListing) {
970     let form: TransferCommunityForm = {
971       community_id: i.props.post.community_id,
972       user_id: i.props.post.creator_id,
973     };
974     WebSocketService.Instance.transferCommunity(form);
975     i.state.showConfirmTransferCommunity = false;
976     i.setState(i.state);
977   }
978
979   handleShowConfirmTransferSite(i: PostListing) {
980     i.state.showConfirmTransferSite = true;
981     i.setState(i.state);
982   }
983
984   handleCancelShowConfirmTransferSite(i: PostListing) {
985     i.state.showConfirmTransferSite = false;
986     i.setState(i.state);
987   }
988
989   handleTransferSite(i: PostListing) {
990     let form: TransferSiteForm = {
991       user_id: i.props.post.creator_id,
992     };
993     WebSocketService.Instance.transferSite(form);
994     i.state.showConfirmTransferSite = false;
995     i.setState(i.state);
996   }
997
998   handleImageExpandClick(i: PostListing) {
999     i.state.imageExpanded = !i.state.imageExpanded;
1000     i.setState(i.state);
1001   }
1002
1003   handleViewSource(i: PostListing) {
1004     i.state.viewSource = !i.state.viewSource;
1005     i.setState(i.state);
1006   }
1007 }