]> Untitled Git - lemmy-ui.git/blob - src/shared/components/community/sidebar.tsx
Re-organized components folder. (#339)
[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(community.id, 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(
261               community_view.community.id,
262               this.handleSubscribe
263             )}
264           >
265             {i18n.t("subscribe")}
266           </a>
267         )}
268       </div>
269     );
270   }
271
272   description() {
273     let description = this.props.community_view.community.description;
274     return (
275       description && (
276         <div
277           className="md-div"
278           dangerouslySetInnerHTML={mdToHtml(description)}
279         />
280       )
281     );
282   }
283
284   adminButtons() {
285     let community_view = this.props.community_view;
286     return (
287       <>
288         <ul class="list-inline mb-1 text-muted font-weight-bold">
289           {this.canMod && (
290             <>
291               <li className="list-inline-item-action">
292                 <button
293                   class="btn btn-link text-muted d-inline-block"
294                   onClick={linkEvent(this, this.handleEditClick)}
295                   data-tippy-content={i18n.t("edit")}
296                   aria-label={i18n.t("edit")}
297                 >
298                   <Icon icon="edit" classes="icon-inline" />
299                 </button>
300               </li>
301               {!this.amTopMod &&
302                 (!this.state.showConfirmLeaveModTeam ? (
303                   <li className="list-inline-item-action">
304                     <button
305                       class="btn btn-link text-muted d-inline-block"
306                       onClick={linkEvent(
307                         this,
308                         this.handleShowConfirmLeaveModTeamClick
309                       )}
310                     >
311                       {i18n.t("leave_mod_team")}
312                     </button>
313                   </li>
314                 ) : (
315                   <>
316                     <li className="list-inline-item-action">
317                       {i18n.t("are_you_sure")}
318                     </li>
319                     <li className="list-inline-item-action">
320                       <button
321                         class="btn btn-link text-muted d-inline-block"
322                         onClick={linkEvent(this, this.handleLeaveModTeamClick)}
323                       >
324                         {i18n.t("yes")}
325                       </button>
326                     </li>
327                     <li className="list-inline-item-action">
328                       <button
329                         class="btn btn-link text-muted d-inline-block"
330                         onClick={linkEvent(
331                           this,
332                           this.handleCancelLeaveModTeamClick
333                         )}
334                       >
335                         {i18n.t("no")}
336                       </button>
337                     </li>
338                   </>
339                 ))}
340               {this.amTopMod && (
341                 <li className="list-inline-item-action">
342                   <button
343                     class="btn btn-link text-muted d-inline-block"
344                     onClick={linkEvent(this, this.handleDeleteClick)}
345                     data-tippy-content={
346                       !community_view.community.deleted
347                         ? i18n.t("delete")
348                         : i18n.t("restore")
349                     }
350                     aria-label={
351                       !community_view.community.deleted
352                         ? i18n.t("delete")
353                         : i18n.t("restore")
354                     }
355                   >
356                     <Icon
357                       icon="trash"
358                       classes={`icon-inline ${
359                         community_view.community.deleted && "text-danger"
360                       }`}
361                     />
362                   </button>
363                 </li>
364               )}
365             </>
366           )}
367           {this.canAdmin && (
368             <li className="list-inline-item">
369               {!this.props.community_view.community.removed ? (
370                 <button
371                   class="btn btn-link text-muted d-inline-block"
372                   onClick={linkEvent(this, this.handleModRemoveShow)}
373                 >
374                   {i18n.t("remove")}
375                 </button>
376               ) : (
377                 <button
378                   class="btn btn-link text-muted d-inline-block"
379                   onClick={linkEvent(this, this.handleModRemoveSubmit)}
380                 >
381                   {i18n.t("restore")}
382                 </button>
383               )}
384             </li>
385           )}
386         </ul>
387         {this.state.showRemoveDialog && (
388           <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
389             <div class="form-group row">
390               <label class="col-form-label" htmlFor="remove-reason">
391                 {i18n.t("reason")}
392               </label>
393               <input
394                 type="text"
395                 id="remove-reason"
396                 class="form-control mr-2"
397                 placeholder={i18n.t("optional")}
398                 value={this.state.removeReason}
399                 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
400               />
401             </div>
402             {/* TODO hold off on expires for now */}
403             {/* <div class="form-group row"> */}
404             {/*   <label class="col-form-label">Expires</label> */}
405             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
406             {/* </div> */}
407             <div class="form-group row">
408               <button type="submit" class="btn btn-secondary">
409                 {i18n.t("remove_community")}
410               </button>
411             </div>
412           </form>
413         )}
414       </>
415     );
416   }
417
418   handleEditClick(i: Sidebar) {
419     i.state.showEdit = true;
420     i.setState(i.state);
421   }
422
423   handleEditCommunity() {
424     this.state.showEdit = false;
425     this.setState(this.state);
426   }
427
428   handleEditCancel() {
429     this.state.showEdit = false;
430     this.setState(this.state);
431   }
432
433   handleDeleteClick(i: Sidebar, event: any) {
434     event.preventDefault();
435     let deleteForm: DeleteCommunity = {
436       community_id: i.props.community_view.community.id,
437       deleted: !i.props.community_view.community.deleted,
438       auth: authField(),
439     };
440     WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
441   }
442
443   handleShowConfirmLeaveModTeamClick(i: Sidebar) {
444     i.state.showConfirmLeaveModTeam = true;
445     i.setState(i.state);
446   }
447
448   handleLeaveModTeamClick(i: Sidebar) {
449     let form: AddModToCommunity = {
450       person_id: UserService.Instance.localUserView.person.id,
451       community_id: i.props.community_view.community.id,
452       added: false,
453       auth: authField(),
454     };
455     WebSocketService.Instance.send(wsClient.addModToCommunity(form));
456     i.state.showConfirmLeaveModTeam = false;
457     i.setState(i.state);
458   }
459
460   handleCancelLeaveModTeamClick(i: Sidebar) {
461     i.state.showConfirmLeaveModTeam = false;
462     i.setState(i.state);
463   }
464
465   handleUnsubscribe(communityId: number, event: any) {
466     event.preventDefault();
467     let form: FollowCommunity = {
468       community_id: communityId,
469       follow: false,
470       auth: authField(),
471     };
472     WebSocketService.Instance.send(wsClient.followCommunity(form));
473   }
474
475   handleSubscribe(communityId: number, event: any) {
476     event.preventDefault();
477     let form: FollowCommunity = {
478       community_id: communityId,
479       follow: true,
480       auth: authField(),
481     };
482     WebSocketService.Instance.send(wsClient.followCommunity(form));
483   }
484
485   private get amTopMod(): boolean {
486     return (
487       this.props.moderators[0].moderator.id ==
488       UserService.Instance.localUserView.person.id
489     );
490   }
491
492   get canMod(): boolean {
493     return (
494       UserService.Instance.localUserView &&
495       this.props.moderators
496         .map(m => m.moderator.id)
497         .includes(UserService.Instance.localUserView.person.id)
498     );
499   }
500
501   get canAdmin(): boolean {
502     return (
503       UserService.Instance.localUserView &&
504       this.props.admins
505         .map(a => a.person.id)
506         .includes(UserService.Instance.localUserView.person.id)
507     );
508   }
509
510   handleModRemoveShow(i: Sidebar) {
511     i.state.showRemoveDialog = true;
512     i.setState(i.state);
513   }
514
515   handleModRemoveReasonChange(i: Sidebar, event: any) {
516     i.state.removeReason = event.target.value;
517     i.setState(i.state);
518   }
519
520   handleModRemoveExpiresChange(i: Sidebar, event: any) {
521     console.log(event.target.value);
522     i.state.removeExpires = event.target.value;
523     i.setState(i.state);
524   }
525
526   handleModRemoveSubmit(i: Sidebar, event: any) {
527     event.preventDefault();
528     let removeForm: RemoveCommunity = {
529       community_id: i.props.community_view.community.id,
530       removed: !i.props.community_view.community.removed,
531       reason: i.state.removeReason,
532       expires: getUnixTime(i.state.removeExpires),
533       auth: authField(),
534     };
535     WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
536
537     i.state.showRemoveDialog = false;
538     i.setState(i.state);
539   }
540 }