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