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