]> Untitled Git - lemmy-ui.git/blob - src/shared/components/sidebar.tsx
Remove categories
[lemmy-ui.git] / src / shared / components / sidebar.tsx
1 import { Component, linkEvent } from "inferno";
2 import { Link } from "inferno-router";
3 import {
4   CommunityView,
5   CommunityModeratorView,
6   FollowCommunity,
7   DeleteCommunity,
8   RemoveCommunity,
9   UserViewSafe,
10   AddModToCommunity,
11 } from "lemmy-js-client";
12 import { WebSocketService, UserService } from "../services";
13 import { mdToHtml, getUnixTime, wsClient, authField } from "../utils";
14 import { CommunityForm } from "./community-form";
15 import { UserListing } from "./user-listing";
16 import { CommunityLink } from "./community-link";
17 import { BannerIconHeader } from "./banner-icon-header";
18 import { Icon } from "./icon";
19 import { i18n } from "../i18next";
20
21 interface SidebarProps {
22   community_view: CommunityView;
23   moderators: CommunityModeratorView[];
24   admins: UserViewSafe[];
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             <UserListing user={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                 <span
293                   role="button"
294                   class="pointer"
295                   onClick={linkEvent(this, this.handleEditClick)}
296                   data-tippy-content={i18n.t("edit")}
297                   aria-label={i18n.t("edit")}
298                 >
299                   <Icon icon="edit" classes="icon-inline" />
300                 </span>
301               </li>
302               {!this.amCreator &&
303                 (!this.state.showConfirmLeaveModTeam ? (
304                   <li className="list-inline-item-action">
305                     <span
306                       class="pointer"
307                       role="button"
308                       onClick={linkEvent(
309                         this,
310                         this.handleShowConfirmLeaveModTeamClick
311                       )}
312                     >
313                       {i18n.t("leave_mod_team")}
314                     </span>
315                   </li>
316                 ) : (
317                   <>
318                     <li className="list-inline-item-action">
319                       {i18n.t("are_you_sure")}
320                     </li>
321                     <li className="list-inline-item-action">
322                       <span
323                         class="pointer"
324                         role="button"
325                         onClick={linkEvent(this, this.handleLeaveModTeamClick)}
326                       >
327                         {i18n.t("yes")}
328                       </span>
329                     </li>
330                     <li className="list-inline-item-action">
331                       <span
332                         class="pointer"
333                         role="button"
334                         onClick={linkEvent(
335                           this,
336                           this.handleCancelLeaveModTeamClick
337                         )}
338                       >
339                         {i18n.t("no")}
340                       </span>
341                     </li>
342                   </>
343                 ))}
344               {this.amCreator && (
345                 <li className="list-inline-item-action">
346                   <span
347                     class="pointer"
348                     onClick={linkEvent(this, this.handleDeleteClick)}
349                     data-tippy-content={
350                       !community_view.community.deleted
351                         ? i18n.t("delete")
352                         : i18n.t("restore")
353                     }
354                     aria-label={
355                       !community_view.community.deleted
356                         ? i18n.t("delete")
357                         : i18n.t("restore")
358                     }
359                   >
360                     <Icon
361                       icon="trash"
362                       classes={`icon-inline ${
363                         community_view.community.deleted && "text-danger"
364                       }`}
365                     />
366                   </span>
367                 </li>
368               )}
369             </>
370           )}
371           {this.canAdmin && (
372             <li className="list-inline-item">
373               {!this.props.community_view.community.removed ? (
374                 <span
375                   class="pointer"
376                   role="button"
377                   onClick={linkEvent(this, this.handleModRemoveShow)}
378                 >
379                   {i18n.t("remove")}
380                 </span>
381               ) : (
382                 <span
383                   class="pointer"
384                   role="button"
385                   onClick={linkEvent(this, this.handleModRemoveSubmit)}
386                 >
387                   {i18n.t("restore")}
388                 </span>
389               )}
390             </li>
391           )}
392         </ul>
393         {this.state.showRemoveDialog && (
394           <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
395             <div class="form-group row">
396               <label class="col-form-label" htmlFor="remove-reason">
397                 {i18n.t("reason")}
398               </label>
399               <input
400                 type="text"
401                 id="remove-reason"
402                 class="form-control mr-2"
403                 placeholder={i18n.t("optional")}
404                 value={this.state.removeReason}
405                 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
406               />
407             </div>
408             {/* TODO hold off on expires for now */}
409             {/* <div class="form-group row"> */}
410             {/*   <label class="col-form-label">Expires</label> */}
411             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
412             {/* </div> */}
413             <div class="form-group row">
414               <button type="submit" class="btn btn-secondary">
415                 {i18n.t("remove_community")}
416               </button>
417             </div>
418           </form>
419         )}
420       </>
421     );
422   }
423
424   handleEditClick(i: Sidebar) {
425     i.state.showEdit = true;
426     i.setState(i.state);
427   }
428
429   handleEditCommunity() {
430     this.state.showEdit = false;
431     this.setState(this.state);
432   }
433
434   handleEditCancel() {
435     this.state.showEdit = false;
436     this.setState(this.state);
437   }
438
439   handleDeleteClick(i: Sidebar, event: any) {
440     event.preventDefault();
441     let deleteForm: DeleteCommunity = {
442       community_id: i.props.community_view.community.id,
443       deleted: !i.props.community_view.community.deleted,
444       auth: authField(),
445     };
446     WebSocketService.Instance.send(wsClient.deleteCommunity(deleteForm));
447   }
448
449   handleShowConfirmLeaveModTeamClick(i: Sidebar) {
450     i.state.showConfirmLeaveModTeam = true;
451     i.setState(i.state);
452   }
453
454   handleLeaveModTeamClick(i: Sidebar) {
455     let form: AddModToCommunity = {
456       user_id: UserService.Instance.user.id,
457       community_id: i.props.community_view.community.id,
458       added: false,
459       auth: authField(),
460     };
461     WebSocketService.Instance.send(wsClient.addModToCommunity(form));
462     i.state.showConfirmLeaveModTeam = false;
463     i.setState(i.state);
464   }
465
466   handleCancelLeaveModTeamClick(i: Sidebar) {
467     i.state.showConfirmLeaveModTeam = false;
468     i.setState(i.state);
469   }
470
471   handleUnsubscribe(communityId: number, event: any) {
472     event.preventDefault();
473     let form: FollowCommunity = {
474       community_id: communityId,
475       follow: false,
476       auth: authField(),
477     };
478     WebSocketService.Instance.send(wsClient.followCommunity(form));
479   }
480
481   handleSubscribe(communityId: number, event: any) {
482     event.preventDefault();
483     let form: FollowCommunity = {
484       community_id: communityId,
485       follow: true,
486       auth: authField(),
487     };
488     WebSocketService.Instance.send(wsClient.followCommunity(form));
489   }
490
491   private get amCreator(): boolean {
492     return this.props.community_view.creator.id == UserService.Instance.user.id;
493   }
494
495   get canMod(): boolean {
496     return (
497       UserService.Instance.user &&
498       this.props.moderators
499         .map(m => m.moderator.id)
500         .includes(UserService.Instance.user.id)
501     );
502   }
503
504   get canAdmin(): boolean {
505     return (
506       UserService.Instance.user &&
507       this.props.admins
508         .map(a => a.user.id)
509         .includes(UserService.Instance.user.id)
510     );
511   }
512
513   handleModRemoveShow(i: Sidebar) {
514     i.state.showRemoveDialog = true;
515     i.setState(i.state);
516   }
517
518   handleModRemoveReasonChange(i: Sidebar, event: any) {
519     i.state.removeReason = event.target.value;
520     i.setState(i.state);
521   }
522
523   handleModRemoveExpiresChange(i: Sidebar, event: any) {
524     console.log(event.target.value);
525     i.state.removeExpires = event.target.value;
526     i.setState(i.state);
527   }
528
529   handleModRemoveSubmit(i: Sidebar, event: any) {
530     event.preventDefault();
531     let removeForm: RemoveCommunity = {
532       community_id: i.props.community_view.community.id,
533       removed: !i.props.community_view.community.removed,
534       reason: i.state.removeReason,
535       expires: getUnixTime(i.state.removeExpires),
536       auth: authField(),
537     };
538     WebSocketService.Instance.send(wsClient.removeCommunity(removeForm));
539
540     i.state.showRemoveDialog = false;
541     i.setState(i.state);
542   }
543 }