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