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