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