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