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