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