]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/sidebar.tsx
Feature/user community block (#362)
[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   CommunityModeratorView,
6   CommunityView,
7   DeleteCommunity,
8   FollowCommunity,
9   PersonViewSafe,
10   RemoveCommunity,
11 } from "lemmy-js-client";
12 import { i18n } from "../../i18next";
13 import { UserService, WebSocketService } from "../../services";
14 import { authField, getUnixTime, mdToHtml, wsClient } from "../../utils";
15 import { BannerIconHeader } from "../common/banner-icon-header";
16 import { Icon } from "../common/icon";
17 import { CommunityForm } from "../community/community-form";
18 import { CommunityLink } from "../community/community-link";
19 import { PersonListing } from "../person/person-listing";
20
21 interface SidebarProps {
22   community_view: CommunityView;
23   moderators: CommunityModeratorView[];
24   admins: PersonViewSafe[];
25   online: number;
26   enableNsfw: boolean;
27   showIcon?: boolean;
28 }
29
30 interface SidebarState {
31   showEdit: boolean;
32   showRemoveDialog: boolean;
33   removeReason: string;
34   removeExpires: string;
35   showConfirmLeaveModTeam: boolean;
36 }
37
38 export class Sidebar extends Component<SidebarProps, SidebarState> {
39   private emptyState: SidebarState = {
40     showEdit: false,
41     showRemoveDialog: false,
42     removeReason: null,
43     removeExpires: null,
44     showConfirmLeaveModTeam: false,
45   };
46
47   constructor(props: any, context: any) {
48     super(props, context);
49     this.state = this.emptyState;
50     this.handleEditCommunity = this.handleEditCommunity.bind(this);
51     this.handleEditCancel = this.handleEditCancel.bind(this);
52   }
53
54   render() {
55     return (
56       <div>
57         {!this.state.showEdit ? (
58           this.sidebar()
59         ) : (
60           <CommunityForm
61             community_view={this.props.community_view}
62             onEdit={this.handleEditCommunity}
63             onCancel={this.handleEditCancel}
64             enableNsfw={this.props.enableNsfw}
65           />
66         )}
67       </div>
68     );
69   }
70
71   sidebar() {
72     return (
73       <div>
74         <div class="card border-secondary mb-3">
75           <div class="card-body">
76             {this.communityTitle()}
77             {this.adminButtons()}
78             {this.subscribe()}
79             {this.createPost()}
80           </div>
81         </div>
82         <div class="card border-secondary mb-3">
83           <div class="card-body">
84             {this.description()}
85             {this.badges()}
86             {this.mods()}
87           </div>
88         </div>
89       </div>
90     );
91   }
92
93   communityTitle() {
94     let community = this.props.community_view.community;
95     let subscribed = this.props.community_view.subscribed;
96     return (
97       <div>
98         <h5 className="mb-0">
99           {this.props.showIcon && (
100             <BannerIconHeader icon={community.icon} banner={community.banner} />
101           )}
102           <span class="mr-2">{community.title}</span>
103           {subscribed && (
104             <a
105               class="btn btn-secondary btn-sm mr-2"
106               href="#"
107               onClick={linkEvent(this, this.handleUnsubscribe)}
108             >
109               <Icon icon="check" classes="icon-inline text-success mr-1" />
110               {i18n.t("joined")}
111             </a>
112           )}
113           {community.removed && (
114             <small className="mr-2 text-muted font-italic">
115               {i18n.t("removed")}
116             </small>
117           )}
118           {community.deleted && (
119             <small className="mr-2 text-muted font-italic">
120               {i18n.t("deleted")}
121             </small>
122           )}
123           {community.nsfw && (
124             <small className="mr-2 text-muted font-italic">
125               {i18n.t("nsfw")}
126             </small>
127           )}
128         </h5>
129         <CommunityLink
130           community={community}
131           realLink
132           useApubName
133           muted
134           hideAvatar
135         />
136       </div>
137     );
138   }
139
140   badges() {
141     let community_view = this.props.community_view;
142     let counts = community_view.counts;
143     return (
144       <ul class="my-1 list-inline">
145         <li className="list-inline-item badge badge-secondary">
146           {i18n.t("number_online", { count: this.props.online })}
147         </li>
148         <li
149           className="list-inline-item badge badge-secondary pointer"
150           data-tippy-content={`${i18n.t("number_of_users", {
151             count: counts.users_active_day,
152           })} ${i18n.t("active_in_the_last")} ${i18n.t("day")}`}
153         >
154           {i18n.t("number_of_users", {
155             count: counts.users_active_day,
156           })}{" "}
157           / {i18n.t("day")}
158         </li>
159         <li
160           className="list-inline-item badge badge-secondary pointer"
161           data-tippy-content={`${i18n.t("number_of_users", {
162             count: counts.users_active_week,
163           })} ${i18n.t("active_in_the_last")} ${i18n.t("week")}`}
164         >
165           {i18n.t("number_of_users", {
166             count: counts.users_active_week,
167           })}{" "}
168           / {i18n.t("week")}
169         </li>
170         <li
171           className="list-inline-item badge badge-secondary pointer"
172           data-tippy-content={`${i18n.t("number_of_users", {
173             count: counts.users_active_month,
174           })} ${i18n.t("active_in_the_last")} ${i18n.t("month")}`}
175         >
176           {i18n.t("number_of_users", {
177             count: counts.users_active_month,
178           })}{" "}
179           / {i18n.t("month")}
180         </li>
181         <li
182           className="list-inline-item badge badge-secondary pointer"
183           data-tippy-content={`${i18n.t("number_of_users", {
184             count: counts.users_active_half_year,
185           })} ${i18n.t("active_in_the_last")} ${i18n.t("number_of_months", {
186             count: 6,
187           })}`}
188         >
189           {i18n.t("number_of_users", {
190             count: counts.users_active_half_year,
191           })}{" "}
192           / {i18n.t("number_of_months", { count: 6 })}
193         </li>
194         <li className="list-inline-item badge badge-secondary">
195           {i18n.t("number_of_subscribers", {
196             count: counts.subscribers,
197           })}
198         </li>
199         <li className="list-inline-item badge badge-secondary">
200           {i18n.t("number_of_posts", {
201             count: counts.posts,
202           })}
203         </li>
204         <li className="list-inline-item badge badge-secondary">
205           {i18n.t("number_of_comments", {
206             count: counts.comments,
207           })}
208         </li>
209         <li className="list-inline-item">
210           <Link
211             className="badge badge-secondary"
212             to={`/modlog/community/${this.props.community_view.community.id}`}
213           >
214             {i18n.t("modlog")}
215           </Link>
216         </li>
217       </ul>
218     );
219   }
220
221   mods() {
222     return (
223       <ul class="list-inline small">
224         <li class="list-inline-item">{i18n.t("mods")}: </li>
225         {this.props.moderators.map(mod => (
226           <li class="list-inline-item">
227             <PersonListing person={mod.moderator} />
228           </li>
229         ))}
230       </ul>
231     );
232   }
233
234   createPost() {
235     let community_view = this.props.community_view;
236     return (
237       community_view.subscribed && (
238         <Link
239           className={`btn btn-secondary btn-block mb-2 ${
240             community_view.community.deleted || community_view.community.removed
241               ? "no-click"
242               : ""
243           }`}
244           to={`/create_post?community_id=${community_view.community.id}`}
245         >
246           {i18n.t("create_a_post")}
247         </Link>
248       )
249     );
250   }
251
252   subscribe() {
253     let community_view = this.props.community_view;
254     return (
255       <div class="mb-2">
256         {!community_view.subscribed && (
257           <a
258             class="btn btn-secondary btn-block"
259             href="#"
260             onClick={linkEvent(this, this.handleSubscribe)}
261           >
262             {i18n.t("subscribe")}
263           </a>
264         )}
265       </div>
266     );
267   }
268
269   description() {
270     let description = this.props.community_view.community.description;
271     return (
272       description && (
273         <div
274           className="md-div"
275           dangerouslySetInnerHTML={mdToHtml(description)}
276         />
277       )
278     );
279   }
280
281   adminButtons() {
282     let community_view = this.props.community_view;
283     return (
284       <>
285         <ul class="list-inline mb-1 text-muted font-weight-bold">
286           {this.canMod && (
287             <>
288               <li className="list-inline-item-action">
289                 <button
290                   class="btn btn-link text-muted d-inline-block"
291                   onClick={linkEvent(this, this.handleEditClick)}
292                   data-tippy-content={i18n.t("edit")}
293                   aria-label={i18n.t("edit")}
294                 >
295                   <Icon icon="edit" classes="icon-inline" />
296                 </button>
297               </li>
298               {!this.amTopMod &&
299                 (!this.state.showConfirmLeaveModTeam ? (
300                   <li className="list-inline-item-action">
301                     <button
302                       class="btn btn-link text-muted d-inline-block"
303                       onClick={linkEvent(
304                         this,
305                         this.handleShowConfirmLeaveModTeamClick
306                       )}
307                     >
308                       {i18n.t("leave_mod_team")}
309                     </button>
310                   </li>
311                 ) : (
312                   <>
313                     <li className="list-inline-item-action">
314                       {i18n.t("are_you_sure")}
315                     </li>
316                     <li className="list-inline-item-action">
317                       <button
318                         class="btn btn-link text-muted d-inline-block"
319                         onClick={linkEvent(this, this.handleLeaveModTeamClick)}
320                       >
321                         {i18n.t("yes")}
322                       </button>
323                     </li>
324                     <li className="list-inline-item-action">
325                       <button
326                         class="btn btn-link text-muted d-inline-block"
327                         onClick={linkEvent(
328                           this,
329                           this.handleCancelLeaveModTeamClick
330                         )}
331                       >
332                         {i18n.t("no")}
333                       </button>
334                     </li>
335                   </>
336                 ))}
337               {this.amTopMod && (
338                 <li className="list-inline-item-action">
339                   <button
340                     class="btn btn-link text-muted d-inline-block"
341                     onClick={linkEvent(this, this.handleDeleteClick)}
342                     data-tippy-content={
343                       !community_view.community.deleted
344                         ? i18n.t("delete")
345                         : i18n.t("restore")
346                     }
347                     aria-label={
348                       !community_view.community.deleted
349                         ? i18n.t("delete")
350                         : i18n.t("restore")
351                     }
352                   >
353                     <Icon
354                       icon="trash"
355                       classes={`icon-inline ${
356                         community_view.community.deleted && "text-danger"
357                       }`}
358                     />
359                   </button>
360                 </li>
361               )}
362             </>
363           )}
364           {this.canAdmin && (
365             <li className="list-inline-item">
366               {!this.props.community_view.community.removed ? (
367                 <button
368                   class="btn btn-link text-muted d-inline-block"
369                   onClick={linkEvent(this, this.handleModRemoveShow)}
370                 >
371                   {i18n.t("remove")}
372                 </button>
373               ) : (
374                 <button
375                   class="btn btn-link text-muted d-inline-block"
376                   onClick={linkEvent(this, this.handleModRemoveSubmit)}
377                 >
378                   {i18n.t("restore")}
379                 </button>
380               )}
381             </li>
382           )}
383         </ul>
384         {this.state.showRemoveDialog && (
385           <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
386             <div class="form-group row">
387               <label class="col-form-label" htmlFor="remove-reason">
388                 {i18n.t("reason")}
389               </label>
390               <input
391                 type="text"
392                 id="remove-reason"
393                 class="form-control mr-2"
394                 placeholder={i18n.t("optional")}
395                 value={this.state.removeReason}
396                 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
397               />
398             </div>
399             {/* TODO hold off on expires for now */}
400             {/* <div class="form-group row"> */}
401             {/*   <label class="col-form-label">Expires</label> */}
402             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
403             {/* </div> */}
404             <div class="form-group row">
405               <button type="submit" class="btn btn-secondary">
406                 {i18n.t("remove_community")}
407               </button>
408             </div>
409           </form>
410         )}
411       </>
412     );
413   }
414
415   handleEditClick(i: Sidebar) {
416     i.state.showEdit = true;
417     i.setState(i.state);
418   }
419
420   handleEditCommunity() {
421     this.state.showEdit = false;
422     this.setState(this.state);
423   }
424
425   handleEditCancel() {
426     this.state.showEdit = false;
427     this.setState(this.state);
428   }
429
430   handleDeleteClick(i: Sidebar, event: any) {
431     event.preventDefault();
432     let deleteForm: DeleteCommunity = {
433       community_id: i.props.community_view.community.id,
434       deleted: !i.props.community_view.community.deleted,
435       auth: authField(),
436     };
437     WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
438   }
439
440   handleShowConfirmLeaveModTeamClick(i: Sidebar) {
441     i.state.showConfirmLeaveModTeam = true;
442     i.setState(i.state);
443   }
444
445   handleLeaveModTeamClick(i: Sidebar) {
446     let form: AddModToCommunity = {
447       person_id: UserService.Instance.myUserInfo.local_user_view.person.id,
448       community_id: i.props.community_view.community.id,
449       added: false,
450       auth: authField(),
451     };
452     WebSocketService.Instance.send(wsClient.addModToCommunity(form));
453     i.state.showConfirmLeaveModTeam = false;
454     i.setState(i.state);
455   }
456
457   handleCancelLeaveModTeamClick(i: Sidebar) {
458     i.state.showConfirmLeaveModTeam = false;
459     i.setState(i.state);
460   }
461
462   handleUnsubscribe(i: Sidebar, event: any) {
463     event.preventDefault();
464     let community_id = i.props.community_view.community.id;
465     let form: FollowCommunity = {
466       community_id,
467       follow: false,
468       auth: authField(),
469     };
470     WebSocketService.Instance.send(wsClient.followCommunity(form));
471
472     // Update myUserInfo
473     UserService.Instance.myUserInfo.follows =
474       UserService.Instance.myUserInfo.follows.filter(
475         i => i.community.id != community_id
476       );
477   }
478
479   handleSubscribe(i: Sidebar, event: any) {
480     event.preventDefault();
481     let community_id = i.props.community_view.community.id;
482     let form: FollowCommunity = {
483       community_id,
484       follow: true,
485       auth: authField(),
486     };
487     WebSocketService.Instance.send(wsClient.followCommunity(form));
488
489     // Update myUserInfo
490     UserService.Instance.myUserInfo.follows.push({
491       community: i.props.community_view.community,
492       follower: UserService.Instance.myUserInfo.local_user_view.person,
493     });
494   }
495
496   private get amTopMod(): boolean {
497     return (
498       this.props.moderators[0].moderator.id ==
499       UserService.Instance.myUserInfo.local_user_view.person.id
500     );
501   }
502
503   get canMod(): boolean {
504     return (
505       UserService.Instance.myUserInfo &&
506       this.props.moderators
507         .map(m => m.moderator.id)
508         .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
509     );
510   }
511
512   get canAdmin(): boolean {
513     return (
514       UserService.Instance.myUserInfo &&
515       this.props.admins
516         .map(a => a.person.id)
517         .includes(UserService.Instance.myUserInfo.local_user_view.person.id)
518     );
519   }
520
521   handleModRemoveShow(i: Sidebar) {
522     i.state.showRemoveDialog = true;
523     i.setState(i.state);
524   }
525
526   handleModRemoveReasonChange(i: Sidebar, event: any) {
527     i.state.removeReason = event.target.value;
528     i.setState(i.state);
529   }
530
531   handleModRemoveExpiresChange(i: Sidebar, event: any) {
532     console.log(event.target.value);
533     i.state.removeExpires = event.target.value;
534     i.setState(i.state);
535   }
536
537   handleModRemoveSubmit(i: Sidebar, event: any) {
538     event.preventDefault();
539     let removeForm: RemoveCommunity = {
540       community_id: i.props.community_view.community.id,
541       removed: !i.props.community_view.community.removed,
542       reason: i.state.removeReason,
543       expires: getUnixTime(i.state.removeExpires),
544       auth: authField(),
545     };
546     WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
547
548     i.state.showRemoveDialog = false;
549     i.setState(i.state);
550   }
551 }