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