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