]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/sidebar.tsx
Refactor lets to consts
[lemmy-ui.git] / src / shared / components / community / sidebar.tsx
1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
3 import {
4   AddModToCommunity,
5   BlockCommunity,
6   CommunityModeratorView,
7   CommunityView,
8   DeleteCommunity,
9   FollowCommunity,
10   Language,
11   PersonView,
12   PurgeCommunity,
13   RemoveCommunity,
14 } from "lemmy-js-client";
15 import { i18n } from "../../i18next";
16 import { UserService, WebSocketService } from "../../services";
17 import {
18   amAdmin,
19   amMod,
20   amTopMod,
21   getUnixTime,
22   hostname,
23   mdToHtml,
24   myAuth,
25   numToSI,
26   wsClient,
27 } from "../../utils";
28 import { BannerIconHeader } from "../common/banner-icon-header";
29 import { Icon, PurgeWarning, Spinner } from "../common/icon";
30 import { CommunityForm } from "../community/community-form";
31 import { CommunityLink } from "../community/community-link";
32 import { PersonListing } from "../person/person-listing";
33
34 interface SidebarProps {
35   community_view: CommunityView;
36   moderators: CommunityModeratorView[];
37   admins: PersonView[];
38   allLanguages: Language[];
39   siteLanguages: number[];
40   communityLanguages?: number[];
41   online: number;
42   enableNsfw?: boolean;
43   showIcon?: boolean;
44   editable?: boolean;
45 }
46
47 interface SidebarState {
48   removeReason?: string;
49   removeExpires?: string;
50   showEdit: boolean;
51   showRemoveDialog: boolean;
52   showPurgeDialog: boolean;
53   purgeReason?: string;
54   purgeLoading: boolean;
55   showConfirmLeaveModTeam: boolean;
56 }
57
58 export class Sidebar extends Component<SidebarProps, SidebarState> {
59   state: SidebarState = {
60     showEdit: false,
61     showRemoveDialog: false,
62     showPurgeDialog: false,
63     purgeLoading: false,
64     showConfirmLeaveModTeam: false,
65   };
66
67   constructor(props: any, context: any) {
68     super(props, context);
69     this.handleEditCommunity = this.handleEditCommunity.bind(this);
70     this.handleEditCancel = this.handleEditCancel.bind(this);
71   }
72
73   render() {
74     return (
75       <div>
76         {!this.state.showEdit ? (
77           this.sidebar()
78         ) : (
79           <CommunityForm
80             community_view={this.props.community_view}
81             allLanguages={this.props.allLanguages}
82             siteLanguages={this.props.siteLanguages}
83             communityLanguages={this.props.communityLanguages}
84             onEdit={this.handleEditCommunity}
85             onCancel={this.handleEditCancel}
86             enableNsfw={this.props.enableNsfw}
87           />
88         )}
89       </div>
90     );
91   }
92
93   sidebar() {
94     const myUSerInfo = UserService.Instance.myUserInfo;
95     const { name, actor_id } = this.props.community_view.community;
96     return (
97       <div>
98         <div className="card border-secondary mb-3">
99           <div className="card-body">
100             {this.communityTitle()}
101             {this.props.editable && this.adminButtons()}
102             {myUSerInfo && this.subscribe()}
103             {this.canPost && this.createPost()}
104             {myUSerInfo && this.blockCommunity()}
105             {!myUSerInfo && (
106               <div className="alert alert-info" role="alert">
107                 {i18n.t("community_not_logged_in_alert", {
108                   community: name,
109                   instance: hostname(actor_id),
110                 })}
111               </div>
112             )}
113           </div>
114         </div>
115         <div className="card border-secondary mb-3">
116           <div className="card-body">
117             {this.description()}
118             {this.badges()}
119             {this.mods()}
120           </div>
121         </div>
122       </div>
123     );
124   }
125
126   communityTitle() {
127     const community = this.props.community_view.community;
128     const subscribed = this.props.community_view.subscribed;
129     return (
130       <div>
131         <h5 className="mb-0">
132           {this.props.showIcon && !community.removed && (
133             <BannerIconHeader icon={community.icon} banner={community.banner} />
134           )}
135           <span className="mr-2">{community.title}</span>
136           {subscribed === "Subscribed" && (
137             <button
138               className="btn btn-secondary btn-sm mr-2"
139               onClick={linkEvent(this, this.handleUnsubscribe)}
140             >
141               <Icon icon="check" classes="icon-inline text-success mr-1" />
142               {i18n.t("joined")}
143             </button>
144           )}
145           {subscribed === "Pending" && (
146             <button
147               className="btn btn-warning mr-2"
148               onClick={linkEvent(this, this.handleUnsubscribe)}
149             >
150               {i18n.t("subscribe_pending")}
151             </button>
152           )}
153           {community.removed && (
154             <small className="mr-2 text-muted font-italic">
155               {i18n.t("removed")}
156             </small>
157           )}
158           {community.deleted && (
159             <small className="mr-2 text-muted font-italic">
160               {i18n.t("deleted")}
161             </small>
162           )}
163           {community.nsfw && (
164             <small className="mr-2 text-muted font-italic">
165               {i18n.t("nsfw")}
166             </small>
167           )}
168         </h5>
169         <CommunityLink
170           community={community}
171           realLink
172           useApubName
173           muted
174           hideAvatar
175         />
176       </div>
177     );
178   }
179
180   badges() {
181     const community_view = this.props.community_view;
182     const counts = community_view.counts;
183     return (
184       <ul className="my-1 list-inline">
185         <li className="list-inline-item badge badge-secondary">
186           {i18n.t("number_online", {
187             count: this.props.online,
188             formattedCount: numToSI(this.props.online),
189           })}
190         </li>
191         <li
192           className="list-inline-item badge badge-secondary pointer"
193           data-tippy-content={i18n.t("active_users_in_the_last_day", {
194             count: Number(counts.users_active_day),
195             formattedCount: numToSI(counts.users_active_day),
196           })}
197         >
198           {i18n.t("number_of_users", {
199             count: Number(counts.users_active_day),
200             formattedCount: numToSI(counts.users_active_day),
201           })}{" "}
202           / {i18n.t("day")}
203         </li>
204         <li
205           className="list-inline-item badge badge-secondary pointer"
206           data-tippy-content={i18n.t("active_users_in_the_last_week", {
207             count: Number(counts.users_active_week),
208             formattedCount: numToSI(counts.users_active_week),
209           })}
210         >
211           {i18n.t("number_of_users", {
212             count: Number(counts.users_active_week),
213             formattedCount: numToSI(counts.users_active_week),
214           })}{" "}
215           / {i18n.t("week")}
216         </li>
217         <li
218           className="list-inline-item badge badge-secondary pointer"
219           data-tippy-content={i18n.t("active_users_in_the_last_month", {
220             count: Number(counts.users_active_month),
221             formattedCount: numToSI(counts.users_active_month),
222           })}
223         >
224           {i18n.t("number_of_users", {
225             count: Number(counts.users_active_month),
226             formattedCount: numToSI(counts.users_active_month),
227           })}{" "}
228           / {i18n.t("month")}
229         </li>
230         <li
231           className="list-inline-item badge badge-secondary pointer"
232           data-tippy-content={i18n.t("active_users_in_the_last_six_months", {
233             count: Number(counts.users_active_half_year),
234             formattedCount: numToSI(counts.users_active_half_year),
235           })}
236         >
237           {i18n.t("number_of_users", {
238             count: Number(counts.users_active_half_year),
239             formattedCount: numToSI(counts.users_active_half_year),
240           })}{" "}
241           / {i18n.t("number_of_months", { count: 6, formattedCount: 6 })}
242         </li>
243         <li className="list-inline-item badge badge-secondary">
244           {i18n.t("number_of_subscribers", {
245             count: Number(counts.subscribers),
246             formattedCount: numToSI(counts.subscribers),
247           })}
248         </li>
249         <li className="list-inline-item badge badge-secondary">
250           {i18n.t("number_of_posts", {
251             count: Number(counts.posts),
252             formattedCount: numToSI(counts.posts),
253           })}
254         </li>
255         <li className="list-inline-item badge badge-secondary">
256           {i18n.t("number_of_comments", {
257             count: Number(counts.comments),
258             formattedCount: numToSI(counts.comments),
259           })}
260         </li>
261         <li className="list-inline-item">
262           <Link
263             className="badge badge-primary"
264             to={`/modlog/${this.props.community_view.community.id}`}
265           >
266             {i18n.t("modlog")}
267           </Link>
268         </li>
269       </ul>
270     );
271   }
272
273   mods() {
274     return (
275       <ul className="list-inline small">
276         <li className="list-inline-item">{i18n.t("mods")}: </li>
277         {this.props.moderators.map(mod => (
278           <li key={mod.moderator.id} className="list-inline-item">
279             <PersonListing person={mod.moderator} />
280           </li>
281         ))}
282       </ul>
283     );
284   }
285
286   createPost() {
287     const cv = this.props.community_view;
288     return (
289       <Link
290         className={`btn btn-secondary btn-block mb-2 ${
291           cv.community.deleted || cv.community.removed ? "no-click" : ""
292         }`}
293         to={`/create_post?communityId=${cv.community.id}`}
294       >
295         {i18n.t("create_a_post")}
296       </Link>
297     );
298   }
299
300   subscribe() {
301     const community_view = this.props.community_view;
302     return (
303       <div className="mb-2">
304         {community_view.subscribed == "NotSubscribed" && (
305           <button
306             className="btn btn-secondary btn-block"
307             onClick={linkEvent(this, this.handleSubscribe)}
308           >
309             {i18n.t("subscribe")}
310           </button>
311         )}
312       </div>
313     );
314   }
315
316   blockCommunity() {
317     const community_view = this.props.community_view;
318     const blocked = this.props.community_view.blocked;
319
320     return (
321       <div className="mb-2">
322         {community_view.subscribed == "NotSubscribed" &&
323           (blocked ? (
324             <button
325               className="btn btn-danger btn-block"
326               onClick={linkEvent(this, this.handleUnblock)}
327             >
328               {i18n.t("unblock_community")}
329             </button>
330           ) : (
331             <button
332               className="btn btn-danger btn-block"
333               onClick={linkEvent(this, this.handleBlock)}
334             >
335               {i18n.t("block_community")}
336             </button>
337           ))}
338       </div>
339     );
340   }
341
342   description() {
343     const desc = this.props.community_view.community.description;
344     return (
345       desc && (
346         <div className="md-div" dangerouslySetInnerHTML={mdToHtml(desc)} />
347       )
348     );
349   }
350
351   adminButtons() {
352     const community_view = this.props.community_view;
353     return (
354       <>
355         <ul className="list-inline mb-1 text-muted font-weight-bold">
356           {amMod(this.props.moderators) && (
357             <>
358               <li className="list-inline-item-action">
359                 <button
360                   className="btn btn-link text-muted d-inline-block"
361                   onClick={linkEvent(this, this.handleEditClick)}
362                   data-tippy-content={i18n.t("edit")}
363                   aria-label={i18n.t("edit")}
364                 >
365                   <Icon icon="edit" classes="icon-inline" />
366                 </button>
367               </li>
368               {!amTopMod(this.props.moderators) &&
369                 (!this.state.showConfirmLeaveModTeam ? (
370                   <li className="list-inline-item-action">
371                     <button
372                       className="btn btn-link text-muted d-inline-block"
373                       onClick={linkEvent(
374                         this,
375                         this.handleShowConfirmLeaveModTeamClick
376                       )}
377                     >
378                       {i18n.t("leave_mod_team")}
379                     </button>
380                   </li>
381                 ) : (
382                   <>
383                     <li className="list-inline-item-action">
384                       {i18n.t("are_you_sure")}
385                     </li>
386                     <li className="list-inline-item-action">
387                       <button
388                         className="btn btn-link text-muted d-inline-block"
389                         onClick={linkEvent(this, this.handleLeaveModTeamClick)}
390                       >
391                         {i18n.t("yes")}
392                       </button>
393                     </li>
394                     <li className="list-inline-item-action">
395                       <button
396                         className="btn btn-link text-muted d-inline-block"
397                         onClick={linkEvent(
398                           this,
399                           this.handleCancelLeaveModTeamClick
400                         )}
401                       >
402                         {i18n.t("no")}
403                       </button>
404                     </li>
405                   </>
406                 ))}
407               {amTopMod(this.props.moderators) && (
408                 <li className="list-inline-item-action">
409                   <button
410                     className="btn btn-link text-muted d-inline-block"
411                     onClick={linkEvent(this, this.handleDeleteClick)}
412                     data-tippy-content={
413                       !community_view.community.deleted
414                         ? i18n.t("delete")
415                         : i18n.t("restore")
416                     }
417                     aria-label={
418                       !community_view.community.deleted
419                         ? i18n.t("delete")
420                         : i18n.t("restore")
421                     }
422                   >
423                     <Icon
424                       icon="trash"
425                       classes={`icon-inline ${
426                         community_view.community.deleted && "text-danger"
427                       }`}
428                     />
429                   </button>
430                 </li>
431               )}
432             </>
433           )}
434           {amAdmin() && (
435             <li className="list-inline-item">
436               {!this.props.community_view.community.removed ? (
437                 <button
438                   className="btn btn-link text-muted d-inline-block"
439                   onClick={linkEvent(this, this.handleModRemoveShow)}
440                 >
441                   {i18n.t("remove")}
442                 </button>
443               ) : (
444                 <button
445                   className="btn btn-link text-muted d-inline-block"
446                   onClick={linkEvent(this, this.handleModRemoveSubmit)}
447                 >
448                   {i18n.t("restore")}
449                 </button>
450               )}
451               <button
452                 className="btn btn-link text-muted d-inline-block"
453                 onClick={linkEvent(this, this.handlePurgeCommunityShow)}
454                 aria-label={i18n.t("purge_community")}
455               >
456                 {i18n.t("purge_community")}
457               </button>
458             </li>
459           )}
460         </ul>
461         {this.state.showRemoveDialog && (
462           <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
463             <div className="form-group">
464               <label className="col-form-label" htmlFor="remove-reason">
465                 {i18n.t("reason")}
466               </label>
467               <input
468                 type="text"
469                 id="remove-reason"
470                 className="form-control mr-2"
471                 placeholder={i18n.t("optional")}
472                 value={this.state.removeReason}
473                 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
474               />
475             </div>
476             {/* TODO hold off on expires for now */}
477             {/* <div class="form-group row"> */}
478             {/*   <label class="col-form-label">Expires</label> */}
479             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
480             {/* </div> */}
481             <div className="form-group">
482               <button type="submit" className="btn btn-secondary">
483                 {i18n.t("remove_community")}
484               </button>
485             </div>
486           </form>
487         )}
488         {this.state.showPurgeDialog && (
489           <form onSubmit={linkEvent(this, this.handlePurgeSubmit)}>
490             <div className="form-group">
491               <PurgeWarning />
492             </div>
493             <div className="form-group">
494               <label className="sr-only" htmlFor="purge-reason">
495                 {i18n.t("reason")}
496               </label>
497               <input
498                 type="text"
499                 id="purge-reason"
500                 className="form-control mr-2"
501                 placeholder={i18n.t("reason")}
502                 value={this.state.purgeReason}
503                 onInput={linkEvent(this, this.handlePurgeReasonChange)}
504               />
505             </div>
506             <div className="form-group">
507               {this.state.purgeLoading ? (
508                 <Spinner />
509               ) : (
510                 <button
511                   type="submit"
512                   className="btn btn-secondary"
513                   aria-label={i18n.t("purge_community")}
514                 >
515                   {i18n.t("purge_community")}
516                 </button>
517               )}
518             </div>
519           </form>
520         )}
521       </>
522     );
523   }
524
525   handleEditClick(i: Sidebar) {
526     i.setState({ showEdit: true });
527   }
528
529   handleEditCommunity() {
530     this.setState({ showEdit: false });
531   }
532
533   handleEditCancel() {
534     this.setState({ showEdit: false });
535   }
536
537   handleDeleteClick(i: Sidebar, event: any) {
538     event.preventDefault();
539     const auth = myAuth();
540     if (auth) {
541       const deleteForm: DeleteCommunity = {
542         community_id: i.props.community_view.community.id,
543         deleted: !i.props.community_view.community.deleted,
544         auth,
545       };
546       WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
547     }
548   }
549
550   handleShowConfirmLeaveModTeamClick(i: Sidebar) {
551     i.setState({ showConfirmLeaveModTeam: true });
552   }
553
554   handleLeaveModTeamClick(i: Sidebar) {
555     const mui = UserService.Instance.myUserInfo;
556     const auth = myAuth();
557     if (auth && mui) {
558       const form: AddModToCommunity = {
559         person_id: mui.local_user_view.person.id,
560         community_id: i.props.community_view.community.id,
561         added: false,
562         auth,
563       };
564       WebSocketService.Instance.send(wsClient.addModToCommunity(form));
565       i.setState({ showConfirmLeaveModTeam: false });
566     }
567   }
568
569   handleCancelLeaveModTeamClick(i: Sidebar) {
570     i.setState({ showConfirmLeaveModTeam: false });
571   }
572
573   handleUnsubscribe(i: Sidebar, event: any) {
574     event.preventDefault();
575     const community_id = i.props.community_view.community.id;
576     const auth = myAuth();
577     if (auth) {
578       const form: FollowCommunity = {
579         community_id,
580         follow: false,
581         auth,
582       };
583       WebSocketService.Instance.send(wsClient.followCommunity(form));
584     }
585
586     // Update myUserInfo
587     const mui = UserService.Instance.myUserInfo;
588     if (mui) {
589       mui.follows = mui.follows.filter(i => i.community.id != community_id);
590     }
591   }
592
593   handleSubscribe(i: Sidebar, event: any) {
594     event.preventDefault();
595     const community_id = i.props.community_view.community.id;
596     const auth = myAuth();
597     if (auth) {
598       const form: FollowCommunity = {
599         community_id,
600         follow: true,
601         auth,
602       };
603       WebSocketService.Instance.send(wsClient.followCommunity(form));
604     }
605
606     // Update myUserInfo
607     const mui = UserService.Instance.myUserInfo;
608     if (mui) {
609       mui.follows.push({
610         community: i.props.community_view.community,
611         follower: mui.local_user_view.person,
612       });
613     }
614   }
615
616   get canPost(): boolean {
617     return (
618       !this.props.community_view.community.posting_restricted_to_mods ||
619       amMod(this.props.moderators) ||
620       amAdmin()
621     );
622   }
623
624   handleModRemoveShow(i: Sidebar) {
625     i.setState({ showRemoveDialog: true });
626   }
627
628   handleModRemoveReasonChange(i: Sidebar, event: any) {
629     i.setState({ removeReason: event.target.value });
630   }
631
632   handleModRemoveExpiresChange(i: Sidebar, event: any) {
633     i.setState({ removeExpires: event.target.value });
634   }
635
636   handleModRemoveSubmit(i: Sidebar, event: any) {
637     event.preventDefault();
638     const auth = myAuth();
639     if (auth) {
640       const removeForm: RemoveCommunity = {
641         community_id: i.props.community_view.community.id,
642         removed: !i.props.community_view.community.removed,
643         reason: i.state.removeReason,
644         expires: getUnixTime(i.state.removeExpires),
645         auth,
646       };
647       WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
648
649       i.setState({ showRemoveDialog: false });
650     }
651   }
652
653   handlePurgeCommunityShow(i: Sidebar) {
654     i.setState({ showPurgeDialog: true, showRemoveDialog: false });
655   }
656
657   handlePurgeReasonChange(i: Sidebar, event: any) {
658     i.setState({ purgeReason: event.target.value });
659   }
660
661   handlePurgeSubmit(i: Sidebar, event: any) {
662     event.preventDefault();
663
664     const auth = myAuth();
665     if (auth) {
666       const form: PurgeCommunity = {
667         community_id: i.props.community_view.community.id,
668         reason: i.state.purgeReason,
669         auth,
670       };
671       WebSocketService.Instance.send(wsClient.purgeCommunity(form));
672       i.setState({ purgeLoading: true });
673     }
674   }
675
676   handleBlock(i: Sidebar, event: any) {
677     event.preventDefault();
678     const auth = myAuth();
679     if (auth) {
680       const blockCommunityForm: BlockCommunity = {
681         community_id: i.props.community_view.community.id,
682         block: true,
683         auth,
684       };
685       WebSocketService.Instance.send(
686         wsClient.blockCommunity(blockCommunityForm)
687       );
688     }
689   }
690
691   handleUnblock(i: Sidebar, event: any) {
692     event.preventDefault();
693     const auth = myAuth();
694     if (auth) {
695       const blockCommunityForm: BlockCommunity = {
696         community_id: i.props.community_view.community.id,
697         block: false,
698         auth,
699       };
700       WebSocketService.Instance.send(
701         wsClient.blockCommunity(blockCommunityForm)
702       );
703     }
704   }
705 }