]> Untitled Git - lemmy-ui.git/blob - src/shared/components/sidebar.tsx
First pass at v2_api
[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 } 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     return (
147       <ul class="my-1 list-inline">
148         <li className="list-inline-item badge badge-secondary">
149           {i18n.t('number_online', { count: this.props.online })}
150         </li>
151         <li className="list-inline-item badge badge-secondary">
152           {i18n.t('number_of_subscribers', {
153             count: community_view.counts.subscribers,
154           })}
155         </li>
156         <li className="list-inline-item badge badge-secondary">
157           {i18n.t('number_of_posts', {
158             count: community_view.counts.posts,
159           })}
160         </li>
161         <li className="list-inline-item badge badge-secondary">
162           {i18n.t('number_of_comments', {
163             count: community_view.counts.comments,
164           })}
165         </li>
166         <li className="list-inline-item">
167           <Link className="badge badge-secondary" to="/communities">
168             {community_view.category.name}
169           </Link>
170         </li>
171         <li className="list-inline-item">
172           <Link
173             className="badge badge-secondary"
174             to={`/modlog/community/${this.props.community_view.community.id}`}
175           >
176             {i18n.t('modlog')}
177           </Link>
178         </li>
179       </ul>
180     );
181   }
182
183   mods() {
184     return (
185       <ul class="list-inline small">
186         <li class="list-inline-item">{i18n.t('mods')}: </li>
187         {this.props.moderators.map(mod => (
188           <li class="list-inline-item">
189             <UserListing user={mod.moderator} />
190           </li>
191         ))}
192       </ul>
193     );
194   }
195
196   createPost() {
197     let community_view = this.props.community_view;
198     return (
199       community_view.subscribed && (
200         <Link
201           className={`btn btn-secondary btn-block mb-2 ${
202             community_view.community.deleted || community_view.community.removed
203               ? 'no-click'
204               : ''
205           }`}
206           to={`/create_post?community_id=${community_view.community.id}`}
207         >
208           {i18n.t('create_a_post')}
209         </Link>
210       )
211     );
212   }
213
214   subscribe() {
215     let community_view = this.props.community_view;
216     return (
217       <div class="mb-2">
218         {!community_view.subscribed && (
219           <a
220             class="btn btn-secondary btn-block"
221             href="#"
222             onClick={linkEvent(
223               community_view.community.id,
224               this.handleSubscribe
225             )}
226           >
227             {i18n.t('subscribe')}
228           </a>
229         )}
230       </div>
231     );
232   }
233
234   description() {
235     let description = this.props.community_view.community.description;
236     return (
237       description && (
238         <div
239           className="md-div"
240           dangerouslySetInnerHTML={mdToHtml(description)}
241         />
242       )
243     );
244   }
245
246   adminButtons() {
247     let community_view = this.props.community_view;
248     return (
249       <>
250         <ul class="list-inline mb-1 text-muted font-weight-bold">
251           {this.canMod && (
252             <>
253               <li className="list-inline-item-action">
254                 <span
255                   class="pointer"
256                   onClick={linkEvent(this, this.handleEditClick)}
257                   data-tippy-content={i18n.t('edit')}
258                 >
259                   <svg class="icon icon-inline">
260                     <use xlinkHref="#icon-edit"></use>
261                   </svg>
262                 </span>
263               </li>
264               {!this.amCreator &&
265                 (!this.state.showConfirmLeaveModTeam ? (
266                   <li className="list-inline-item-action">
267                     <span
268                       class="pointer"
269                       onClick={linkEvent(
270                         this,
271                         this.handleShowConfirmLeaveModTeamClick
272                       )}
273                     >
274                       {i18n.t('leave_mod_team')}
275                     </span>
276                   </li>
277                 ) : (
278                   <>
279                     <li className="list-inline-item-action">
280                       {i18n.t('are_you_sure')}
281                     </li>
282                     <li className="list-inline-item-action">
283                       <span
284                         class="pointer"
285                         onClick={linkEvent(this, this.handleLeaveModTeamClick)}
286                       >
287                         {i18n.t('yes')}
288                       </span>
289                     </li>
290                     <li className="list-inline-item-action">
291                       <span
292                         class="pointer"
293                         onClick={linkEvent(
294                           this,
295                           this.handleCancelLeaveModTeamClick
296                         )}
297                       >
298                         {i18n.t('no')}
299                       </span>
300                     </li>
301                   </>
302                 ))}
303               {this.amCreator && (
304                 <li className="list-inline-item-action">
305                   <span
306                     class="pointer"
307                     onClick={linkEvent(this, this.handleDeleteClick)}
308                     data-tippy-content={
309                       !community_view.community.deleted
310                         ? i18n.t('delete')
311                         : i18n.t('restore')
312                     }
313                   >
314                     <svg
315                       class={`icon icon-inline ${
316                         community_view.community.deleted && 'text-danger'
317                       }`}
318                     >
319                       <use xlinkHref="#icon-trash"></use>
320                     </svg>
321                   </span>
322                 </li>
323               )}
324             </>
325           )}
326           {this.canAdmin && (
327             <li className="list-inline-item">
328               {!this.props.community_view.community.removed ? (
329                 <span
330                   class="pointer"
331                   onClick={linkEvent(this, this.handleModRemoveShow)}
332                 >
333                   {i18n.t('remove')}
334                 </span>
335               ) : (
336                 <span
337                   class="pointer"
338                   onClick={linkEvent(this, this.handleModRemoveSubmit)}
339                 >
340                   {i18n.t('restore')}
341                 </span>
342               )}
343             </li>
344           )}
345         </ul>
346         {this.state.showRemoveDialog && (
347           <form onSubmit={linkEvent(this, this.handleModRemoveSubmit)}>
348             <div class="form-group row">
349               <label class="col-form-label" htmlFor="remove-reason">
350                 {i18n.t('reason')}
351               </label>
352               <input
353                 type="text"
354                 id="remove-reason"
355                 class="form-control mr-2"
356                 placeholder={i18n.t('optional')}
357                 value={this.state.removeReason}
358                 onInput={linkEvent(this, this.handleModRemoveReasonChange)}
359               />
360             </div>
361             {/* TODO hold off on expires for now */}
362             {/* <div class="form-group row"> */}
363             {/*   <label class="col-form-label">Expires</label> */}
364             {/*   <input type="date" class="form-control mr-2" placeholder={i18n.t('expires')} value={this.state.removeExpires} onInput={linkEvent(this, this.handleModRemoveExpiresChange)} /> */}
365             {/* </div> */}
366             <div class="form-group row">
367               <button type="submit" class="btn btn-secondary">
368                 {i18n.t('remove_community')}
369               </button>
370             </div>
371           </form>
372         )}
373       </>
374     );
375   }
376
377   handleEditClick(i: Sidebar) {
378     i.state.showEdit = true;
379     i.setState(i.state);
380   }
381
382   handleEditCommunity() {
383     this.state.showEdit = false;
384     this.setState(this.state);
385   }
386
387   handleEditCancel() {
388     this.state.showEdit = false;
389     this.setState(this.state);
390   }
391
392   handleDeleteClick(i: Sidebar, event: any) {
393     event.preventDefault();
394     let deleteForm: DeleteCommunity = {
395       edit_id: i.props.community_view.community.id,
396       deleted: !i.props.community_view.community.deleted,
397       auth: UserService.Instance.authField(),
398     };
399     WebSocketService.Instance.client.deleteCommunity(deleteForm);
400   }
401
402   handleShowConfirmLeaveModTeamClick(i: Sidebar) {
403     i.state.showConfirmLeaveModTeam = true;
404     i.setState(i.state);
405   }
406
407   handleLeaveModTeamClick(i: Sidebar) {
408     let form: AddModToCommunity = {
409       user_id: UserService.Instance.user.id,
410       community_id: i.props.community_view.community.id,
411       added: false,
412       auth: UserService.Instance.authField(),
413     };
414     WebSocketService.Instance.client.addModToCommunity(form);
415     i.state.showConfirmLeaveModTeam = false;
416     i.setState(i.state);
417   }
418
419   handleCancelLeaveModTeamClick(i: Sidebar) {
420     i.state.showConfirmLeaveModTeam = false;
421     i.setState(i.state);
422   }
423
424   handleUnsubscribe(communityId: number, event: any) {
425     event.preventDefault();
426     let form: FollowCommunity = {
427       community_id: communityId,
428       follow: false,
429       auth: UserService.Instance.authField(),
430     };
431     WebSocketService.Instance.client.followCommunity(form);
432   }
433
434   handleSubscribe(communityId: number, event: any) {
435     event.preventDefault();
436     let form: FollowCommunity = {
437       community_id: communityId,
438       follow: true,
439       auth: UserService.Instance.authField(),
440     };
441     WebSocketService.Instance.client.followCommunity(form);
442   }
443
444   private get amCreator(): boolean {
445     return this.props.community_view.creator.id == UserService.Instance.user.id;
446   }
447
448   get canMod(): boolean {
449     return (
450       UserService.Instance.user &&
451       this.props.moderators
452         .map(m => m.moderator.id)
453         .includes(UserService.Instance.user.id)
454     );
455   }
456
457   get canAdmin(): boolean {
458     return (
459       UserService.Instance.user &&
460       this.props.admins
461         .map(a => a.user.id)
462         .includes(UserService.Instance.user.id)
463     );
464   }
465
466   handleModRemoveShow(i: Sidebar) {
467     i.state.showRemoveDialog = true;
468     i.setState(i.state);
469   }
470
471   handleModRemoveReasonChange(i: Sidebar, event: any) {
472     i.state.removeReason = event.target.value;
473     i.setState(i.state);
474   }
475
476   handleModRemoveExpiresChange(i: Sidebar, event: any) {
477     console.log(event.target.value);
478     i.state.removeExpires = event.target.value;
479     i.setState(i.state);
480   }
481
482   handleModRemoveSubmit(i: Sidebar, event: any) {
483     event.preventDefault();
484     let removeForm: RemoveCommunity = {
485       edit_id: i.props.community_view.community.id,
486       removed: !i.props.community_view.community.removed,
487       reason: i.state.removeReason,
488       expires: getUnixTime(i.state.removeExpires),
489       auth: UserService.Instance.authField(),
490     };
491     WebSocketService.Instance.client.removeCommunity(removeForm);
492
493     i.state.showRemoveDialog = false;
494     i.setState(i.state);
495   }
496 }