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